From acae93c8b8ea0d2a9bdfe764dcc4ab2821edfffe Mon Sep 17 00:00:00 2001 From: David Lopez Date: Mon, 2 Oct 2023 16:09:24 -0700 Subject: [PATCH] feat: support amplify js v6 api changes (#1101) Co-authored-by: David Lopez --- ...studio-ui-codegen-react-forms.test.ts.snap | 10032 ++++++++++------ .../studio-ui-codegen-react.test.ts.snap | 255 + .../studio-ui-codegen-react-forms.test.ts | 87 + .../__tests__/studio-ui-codegen-react.test.ts | 25 + .../lib/amplify-ui-renderers/collection.ts | 5 +- .../lib/amplify-ui-renderers/form.ts | 7 +- .../bidirectional-relationship.ts | 31 +- .../forms/form-renderer-helper/cta-props.ts | 50 +- .../form-renderer-helper/relationship.ts | 194 +- .../lib/forms/react-form-renderer.ts | 22 +- .../lib/imports/import-collection.ts | 4 + .../react-studio-template-renderer-helper.ts | 18 + .../lib/react-studio-template-renderer.ts | 41 +- .../codegen-ui-react/lib/utils/graphql.ts | 32 +- .../codegen-ui-react/lib/workflow/action.ts | 6 +- 15 files changed, 7067 insertions(+), 3742 deletions(-) diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap index 7566aaa3b..812400b11 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap @@ -550,10 +550,11 @@ export default function CreateOwnerForm(props: CreateOwnerFormProps): React.Reac " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should 1:1 relationships without types file path - Create amplify js v6 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { + Autocomplete, Badge, Button, Divider, @@ -562,14 +563,15 @@ import { Icon, ScrollView, Text, - TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { API } from \\"aws-amplify\\"; -import { createPost } from \\"../graphql/mutations\\"; +import { generateClient } from \\"aws-amplify/api\\"; +import { listDogs } from \\"../graphql/queries\\"; +import { createOwner, updateDog, updateOwner } from \\"../graphql/mutations\\"; +const client = generateClient(); function ArrayField({ items = [], onChange, @@ -725,63 +727,52 @@ function ArrayField({ ); } -export default function MyPostForm(props) { +export default function CreateOwnerForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - caption: \\"\\", - username: \\"\\", - post_url: \\"\\", - metadata: \\"\\", - profile_url: \\"\\", - nonModelField: \\"\\", - nonModelFieldArray: [], + name: \\"\\", + Dog: undefined, }; - const [caption, setCaption] = React.useState(initialValues.caption); - const [username, setUsername] = React.useState(initialValues.username); - const [post_url, setPost_url] = React.useState(initialValues.post_url); - const [metadata, setMetadata] = React.useState(initialValues.metadata); - const [profile_url, setProfile_url] = React.useState( - initialValues.profile_url - ); - const [nonModelField, setNonModelField] = React.useState( - initialValues.nonModelField - ); - const [nonModelFieldArray, setNonModelFieldArray] = React.useState( - initialValues.nonModelFieldArray - ); + const [name, setName] = React.useState(initialValues.name); + const [Dog, setDog] = React.useState(initialValues.Dog); + const [DogLoading, setDogLoading] = React.useState(false); + const [DogRecords, setDogRecords] = React.useState([]); + const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setCaption(initialValues.caption); - setUsername(initialValues.username); - setPost_url(initialValues.post_url); - setMetadata(initialValues.metadata); - setProfile_url(initialValues.profile_url); - setNonModelField(initialValues.nonModelField); - setNonModelFieldArray(initialValues.nonModelFieldArray); - setCurrentNonModelFieldArrayValue(\\"\\"); + setName(initialValues.name); + setDog(initialValues.Dog); + setCurrentDogValue(undefined); + setCurrentDogDisplayValue(\\"\\"); setErrors({}); }; - const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = + const [currentDogDisplayValue, setCurrentDogDisplayValue] = React.useState(\\"\\"); - const nonModelFieldArrayRef = React.createRef(); + const [currentDogValue, setCurrentDogValue] = React.useState(undefined); + const DogRef = React.createRef(); + const getIDValue = { + Dog: (r) => JSON.stringify({ id: r?.id }), + }; + const DogIdSet = new Set( + Array.isArray(Dog) + ? Dog.map((r) => getIDValue.Dog?.(r)) + : getIDValue.Dog?.(Dog) + ); + const getDisplayValue = { + Dog: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, + }; const validations = { - caption: [], - username: [], - post_url: [{ type: \\"URL\\" }], - metadata: [{ type: \\"JSON\\" }], - profile_url: [{ type: \\"URL\\" }], - nonModelField: [{ type: \\"JSON\\" }], - nonModelFieldArray: [{ type: \\"JSON\\" }], + name: [{ type: \\"Required\\" }], + Dog: [], }; const runValidationTasks = async ( fieldName, @@ -800,6 +791,38 @@ export default function MyPostForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; + const fetchDogRecords = async (value) => { + setDogLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ name: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await client.graphql({ + query: listDogs.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listDogs?.items; + var loaded = result.filter( + (item) => !DogIdSet.has(getIDValue.Dog?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setDogRecords(newOptions.slice(0, autocompleteLength)); + setDogLoading(false); + }; + React.useEffect(() => { + fetchDogRecords(\\"\\"); + }, []); return ( { event.preventDefault(); let modelFields = { - caption, - username, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, + name, + Dog, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks(fieldName, item) + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) ) ); return promises; } promises.push( - runValidationTasks(fieldName, modelFields[fieldName]) + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) ); return promises; }, []) @@ -846,26 +872,49 @@ export default function MyPostForm(props) { } }); const modelFieldsToSave = { - caption: modelFields.caption, - username: modelFields.username, - post_url: modelFields.post_url, - metadata: modelFields.metadata, - profile_url: modelFields.profile_url, - nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => - JSON.parse(s) - ), - nonModelField: modelFields.nonModelField - ? JSON.parse(modelFields.nonModelField) - : modelFields.nonModelField, + name: modelFields.name, + ownerDogId: modelFields?.Dog?.id, }; - await API.graphql({ - query: createPost.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - ...modelFieldsToSave, + const owner = ( + await client.graphql({ + query: createOwner.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFieldsToSave, + }, }, - }, - }); + }) + )?.data?.createOwner; + const promises = []; + const dogToLink = modelFields.Dog; + if (dogToLink) { + promises.push( + client.graphql({ + query: updateDog.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: Dog.id, + dogOwnerId: owner.id, + }, + }, + }) + ); + const ownerToUnlink = await dogToLink.owner; + if (ownerToUnlink) { + promises.push( + client.graphql({ + query: updateOwner.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: ownerToUnlink.id, + ownerDogId: null, + }, + }, + }) + ); + } + } + await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -879,340 +928,2827 @@ export default function MyPostForm(props) { } } }} - {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...getOverrideProps(overrides, \\"CreateOwnerForm\\")} {...rest} > - - - - - - - { let { value } = e.target; if (onChange) { const modelFields = { - caption: value, - username, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, + name: value, + Dog, }; const result = onChange(modelFields); - value = result?.caption ?? value; + value = result?.name ?? value; } - if (errors.caption?.hasError) { - runValidationTasks(\\"caption\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setCaption(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"caption\\", caption)} - errorMessage={errors.caption?.errorMessage} - hasError={errors.caption?.hasError} - {...getOverrideProps(overrides, \\"caption\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} > - { - let { value } = e.target; + { + let value = items[0]; if (onChange) { const modelFields = { - caption, - username: value, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, + name, + Dog: value, }; const result = onChange(modelFields); - value = result?.username ?? value; + value = result?.Dog ?? value; + } + setDog(value); + setCurrentDogValue(undefined); + setCurrentDogDisplayValue(\\"\\"); + }} + currentFieldValue={currentDogValue} + label={\\"Dog\\"} + items={Dog ? [Dog] : []} + hasError={errors?.Dog?.hasError} + runValidationTasks={async () => + await runValidationTasks(\\"Dog\\", currentDogValue) + } + errorMessage={errors?.Dog?.errorMessage} + getBadgeText={getDisplayValue.Dog} + setFieldValue={(model) => { + setCurrentDogDisplayValue(model ? getDisplayValue.Dog(model) : \\"\\"); + setCurrentDogValue(model); + }} + inputFieldRef={DogRef} + defaultFieldValue={\\"\\"} + > + !DogIdSet.has(getIDValue.Dog?.(r)) + ).map((r) => ({ + id: getIDValue.Dog?.(r), + label: getDisplayValue.Dog?.(r), + }))} + isLoading={DogLoading} + onSelect={({ id, label }) => { + setCurrentDogValue( + DogRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentDogDisplayValue(label); + runValidationTasks(\\"Dog\\", label); + }} + onClear={() => { + setCurrentDogDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchDogRecords(value); + if (errors.Dog?.hasError) { + runValidationTasks(\\"Dog\\", value); + } + setCurrentDogDisplayValue(value); + setCurrentDogValue(undefined); + }} + onBlur={() => runValidationTasks(\\"Dog\\", currentDogDisplayValue)} + errorMessage={errors.Dog?.errorMessage} + hasError={errors.Dog?.hasError} + ref={DogRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"Dog\\")} + > + + + + + + + + + ); +} +" +`; + +exports[`amplify form renderer tests GraphQL form tests should 1:1 relationships without types file path - Create amplify js v6 2`] = ` +"import * as React from \\"react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type CreateOwnerFormInputValues = { + name?: string; + Dog?: any; +}; +export declare type CreateOwnerFormValidationValues = { + name?: ValidationFunction; + Dog?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type CreateOwnerFormOverridesProps = { + CreateOwnerFormGrid?: PrimitiveOverrideProps; + name?: PrimitiveOverrideProps; + Dog?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type CreateOwnerFormProps = React.PropsWithChildren<{ + overrides?: CreateOwnerFormOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; + onSuccess?: (fields: CreateOwnerFormInputValues) => void; + onError?: (fields: CreateOwnerFormInputValues, errorMessage: string) => void; + onChange?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; + onValidate?: CreateOwnerFormValidationValues; +} & React.CSSProperties>; +export default function CreateOwnerForm(props: CreateOwnerFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form - amplify js v6 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextAreaField, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { generateClient } from \\"aws-amplify/api\\"; +import { createPost } from \\"../graphql/mutations\\"; +const client = generateClient(); +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + runValidationTasks, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + const { hasError } = runValidationTasks(); + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function MyPostForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + caption: \\"\\", + username: \\"\\", + post_url: \\"\\", + metadata: \\"\\", + profile_url: \\"\\", + nonModelField: \\"\\", + nonModelFieldArray: [], + }; + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState(initialValues.metadata); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [nonModelField, setNonModelField] = React.useState( + initialValues.nonModelField + ); + const [nonModelFieldArray, setNonModelFieldArray] = React.useState( + initialValues.nonModelFieldArray + ); + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setCaption(initialValues.caption); + setUsername(initialValues.username); + setPost_url(initialValues.post_url); + setMetadata(initialValues.metadata); + setProfile_url(initialValues.profile_url); + setNonModelField(initialValues.nonModelField); + setNonModelFieldArray(initialValues.nonModelFieldArray); + setCurrentNonModelFieldArrayValue(\\"\\"); + setErrors({}); + }; + const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = + React.useState(\\"\\"); + const nonModelFieldArrayRef = React.createRef(); + const validations = { + caption: [], + username: [], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], + profile_url: [{ type: \\"URL\\" }], + nonModelField: [{ type: \\"JSON\\" }], + nonModelFieldArray: [{ type: \\"JSON\\" }], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + return ( + { + event.preventDefault(); + let modelFields = { + caption, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value === \\"\\") { + modelFields[key] = null; + } + }); + const modelFieldsToSave = { + caption: modelFields.caption, + username: modelFields.username, + post_url: modelFields.post_url, + metadata: modelFields.metadata, + profile_url: modelFields.profile_url, + nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => + JSON.parse(s) + ), + nonModelField: modelFields.nonModelField + ? JSON.parse(modelFields.nonModelField) + : modelFields.nonModelField, + }; + await client.graphql({ + query: createPost.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFieldsToSave, + }, + }, + }); + if (onSuccess) { + onSuccess(modelFields); + } + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { + const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); + onError(modelFields, messages); + } + } + }} + {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...rest} + > + + + + + + + + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption: value, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.caption ?? value; + } + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); + } + setCaption(value); + }} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username: value, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.username ?? value; + } + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); + } + setUsername(value); + }} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url: value, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; + } + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata: value, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.metadata ?? value; + } + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); + } + setMetadata(value); + }} + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url: value, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url, + nonModelField: value, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.nonModelField ?? value; + } + if (errors.nonModelField?.hasError) { + runValidationTasks(\\"nonModelField\\", value); + } + setNonModelField(value); + }} + onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} + errorMessage={errors.nonModelField?.errorMessage} + hasError={errors.nonModelField?.hasError} + {...getOverrideProps(overrides, \\"nonModelField\\")} + > + { + let values = items; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray: values, + }; + const result = onChange(modelFields); + values = result?.nonModelFieldArray ?? values; + } + setNonModelFieldArray(values); + setCurrentNonModelFieldArrayValue(\\"\\"); + }} + currentFieldValue={currentNonModelFieldArrayValue} + label={\\"Non model field array\\"} + items={nonModelFieldArray} + hasError={errors?.nonModelFieldArray?.hasError} + runValidationTasks={async () => + await runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) + } + errorMessage={errors?.nonModelFieldArray?.errorMessage} + setFieldValue={setCurrentNonModelFieldArrayValue} + inputFieldRef={nonModelFieldArrayRef} + defaultFieldValue={\\"\\"} + > + { + let { value } = e.target; + if (errors.nonModelFieldArray?.hasError) { + runValidationTasks(\\"nonModelFieldArray\\", value); + } + setCurrentNonModelFieldArrayValue(value); + }} + onBlur={() => + runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) + } + errorMessage={errors.nonModelFieldArray?.errorMessage} + hasError={errors.nonModelFieldArray?.hasError} + ref={nonModelFieldArrayRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} + > + + + ); +} +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form - amplify js v6 2`] = ` +"import * as React from \\"react\\"; +import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type MyPostFormInputValues = { + caption?: string; + username?: string; + post_url?: string; + metadata?: string; + profile_url?: string; + nonModelField?: string; + nonModelFieldArray?: string[]; +}; +export declare type MyPostFormValidationValues = { + caption?: ValidationFunction; + username?: ValidationFunction; + post_url?: ValidationFunction; + metadata?: ValidationFunction; + profile_url?: ValidationFunction; + nonModelField?: ValidationFunction; + nonModelFieldArray?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: PrimitiveOverrideProps; + caption?: PrimitiveOverrideProps; + username?: PrimitiveOverrideProps; + post_url?: PrimitiveOverrideProps; + metadata?: PrimitiveOverrideProps; + profile_url?: PrimitiveOverrideProps; + nonModelField?: PrimitiveOverrideProps; + nonModelFieldArray?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; +} & React.CSSProperties>; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextAreaField, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { API } from \\"aws-amplify\\"; +import { createPost } from \\"../graphql/mutations\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + runValidationTasks, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + const { hasError } = runValidationTasks(); + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function MyPostForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + caption: \\"\\", + username: \\"\\", + post_url: \\"\\", + metadata: \\"\\", + profile_url: \\"\\", + nonModelField: \\"\\", + nonModelFieldArray: [], + }; + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState(initialValues.metadata); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [nonModelField, setNonModelField] = React.useState( + initialValues.nonModelField + ); + const [nonModelFieldArray, setNonModelFieldArray] = React.useState( + initialValues.nonModelFieldArray + ); + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setCaption(initialValues.caption); + setUsername(initialValues.username); + setPost_url(initialValues.post_url); + setMetadata(initialValues.metadata); + setProfile_url(initialValues.profile_url); + setNonModelField(initialValues.nonModelField); + setNonModelFieldArray(initialValues.nonModelFieldArray); + setCurrentNonModelFieldArrayValue(\\"\\"); + setErrors({}); + }; + const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = + React.useState(\\"\\"); + const nonModelFieldArrayRef = React.createRef(); + const validations = { + caption: [], + username: [], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], + profile_url: [{ type: \\"URL\\" }], + nonModelField: [{ type: \\"JSON\\" }], + nonModelFieldArray: [{ type: \\"JSON\\" }], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + return ( + { + event.preventDefault(); + let modelFields = { + caption, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value === \\"\\") { + modelFields[key] = null; + } + }); + const modelFieldsToSave = { + caption: modelFields.caption, + username: modelFields.username, + post_url: modelFields.post_url, + metadata: modelFields.metadata, + profile_url: modelFields.profile_url, + nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => + JSON.parse(s) + ), + nonModelField: modelFields.nonModelField + ? JSON.parse(modelFields.nonModelField) + : modelFields.nonModelField, + }; + await API.graphql({ + query: createPost.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFieldsToSave, + }, + }, + }); + if (onSuccess) { + onSuccess(modelFields); + } + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { + const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); + onError(modelFields, messages); + } + } + }} + {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...rest} + > + + + + + + + + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption: value, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.caption ?? value; + } + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); + } + setCaption(value); + }} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username: value, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.username ?? value; + } + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); + } + setUsername(value); + }} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url: value, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; + } + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata: value, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.metadata ?? value; + } + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); + } + setMetadata(value); + }} + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url: value, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url, + nonModelField: value, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.nonModelField ?? value; + } + if (errors.nonModelField?.hasError) { + runValidationTasks(\\"nonModelField\\", value); + } + setNonModelField(value); + }} + onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} + errorMessage={errors.nonModelField?.errorMessage} + hasError={errors.nonModelField?.hasError} + {...getOverrideProps(overrides, \\"nonModelField\\")} + > + { + let values = items; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray: values, + }; + const result = onChange(modelFields); + values = result?.nonModelFieldArray ?? values; + } + setNonModelFieldArray(values); + setCurrentNonModelFieldArrayValue(\\"\\"); + }} + currentFieldValue={currentNonModelFieldArrayValue} + label={\\"Non model field array\\"} + items={nonModelFieldArray} + hasError={errors?.nonModelFieldArray?.hasError} + runValidationTasks={async () => + await runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) + } + errorMessage={errors?.nonModelFieldArray?.errorMessage} + setFieldValue={setCurrentNonModelFieldArrayValue} + inputFieldRef={nonModelFieldArrayRef} + defaultFieldValue={\\"\\"} + > + { + let { value } = e.target; + if (errors.nonModelFieldArray?.hasError) { + runValidationTasks(\\"nonModelFieldArray\\", value); + } + setCurrentNonModelFieldArrayValue(value); + }} + onBlur={() => + runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) + } + errorMessage={errors.nonModelFieldArray?.errorMessage} + hasError={errors.nonModelFieldArray?.hasError} + ref={nonModelFieldArrayRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} + > + + + ); +} +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form 2`] = ` +"import * as React from \\"react\\"; +import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type MyPostFormInputValues = { + caption?: string; + username?: string; + post_url?: string; + metadata?: string; + profile_url?: string; + nonModelField?: string; + nonModelFieldArray?: string[]; +}; +export declare type MyPostFormValidationValues = { + caption?: ValidationFunction; + username?: ValidationFunction; + post_url?: ValidationFunction; + metadata?: ValidationFunction; + profile_url?: ValidationFunction; + nonModelField?: ValidationFunction; + nonModelFieldArray?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: PrimitiveOverrideProps; + caption?: PrimitiveOverrideProps; + username?: PrimitiveOverrideProps; + post_url?: PrimitiveOverrideProps; + metadata?: PrimitiveOverrideProps; + profile_url?: PrimitiveOverrideProps; + nonModelField?: PrimitiveOverrideProps; + nonModelFieldArray?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; +} & React.CSSProperties>; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { API } from \\"aws-amplify\\"; +import { listTeams } from \\"../graphql/queries\\"; +import { createMember } from \\"../graphql/mutations\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + runValidationTasks, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + const { hasError } = runValidationTasks(); + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function MyMemberForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + name: \\"\\", + teamID: undefined, + Team: undefined, + }; + const [name, setName] = React.useState(initialValues.name); + const [teamID, setTeamID] = React.useState(initialValues.teamID); + const [teamIDLoading, setTeamIDLoading] = React.useState(false); + const [teamIDRecords, setTeamIDRecords] = React.useState([]); + const [selectedTeamIDRecords, setSelectedTeamIDRecords] = React.useState([]); + const [Team, setTeam] = React.useState(initialValues.Team); + const [TeamLoading, setTeamLoading] = React.useState(false); + const [TeamRecords, setTeamRecords] = React.useState([]); + const autocompleteLength = 10; + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setName(initialValues.name); + setTeamID(initialValues.teamID); + setCurrentTeamIDValue(undefined); + setCurrentTeamIDDisplayValue(\\"\\"); + setTeam(initialValues.Team); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(\\"\\"); + setErrors({}); + }; + const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = + React.useState(\\"\\"); + const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); + const teamIDRef = React.createRef(); + const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = + React.useState(\\"\\"); + const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); + const TeamRef = React.createRef(); + const getIDValue = { + Team: (r) => JSON.stringify({ id: r?.id }), + }; + const TeamIdSet = new Set( + Array.isArray(Team) + ? Team.map((r) => getIDValue.Team?.(r)) + : getIDValue.Team?.(Team) + ); + const getDisplayValue = { + teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, + Team: (r) => r?.name, + }; + const validations = { + name: [], + teamID: [{ type: \\"Required\\" }], + Team: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + const fetchTeamIDRecords = async (value) => { + setTeamIDLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ name: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTeams.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listTeams?.items; + var loaded = result.filter((item) => teamID !== item.id); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setTeamIDRecords(newOptions.slice(0, autocompleteLength)); + setTeamIDLoading(false); + }; + const fetchTeamRecords = async (value) => { + setTeamLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ name: { contains: value } }] }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTeams.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listTeams?.items; + var loaded = result.filter( + (item) => !TeamIdSet.has(getIDValue.Team?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setTeamRecords(newOptions.slice(0, autocompleteLength)); + setTeamLoading(false); + }; + React.useEffect(() => { + fetchTeamIDRecords(\\"\\"); + fetchTeamRecords(\\"\\"); + }, []); + return ( + { + event.preventDefault(); + let modelFields = { + name, + teamID, + Team, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value === \\"\\") { + modelFields[key] = null; + } + }); + const modelFieldsToSave = { + name: modelFields.name, + teamID: modelFields.teamID, + teamMembersId: modelFields?.Team?.id, + }; + await API.graphql({ + query: createMember.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFieldsToSave, + }, + }, + }); + if (onSuccess) { + onSuccess(modelFields); + } + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { + const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); + onError(modelFields, messages); + } + } + }} + {...getOverrideProps(overrides, \\"MyMemberForm\\")} + {...rest} + > + + + + + + + + { + let { value } = e.target; + if (onChange) { + const modelFields = { + name: value, + teamID, + Team, + }; + const result = onChange(modelFields); + value = result?.name ?? value; + } + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); + } + setName(value); + }} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} + > + { + let value = items[0]; + if (onChange) { + const modelFields = { + name, + teamID: value, + Team, + }; + const result = onChange(modelFields); + value = result?.teamID ?? value; + } + setTeamID(value); + setCurrentTeamIDValue(undefined); + }} + currentFieldValue={currentTeamIDValue} + label={\\"Team id\\"} + items={teamID ? [teamID] : []} + hasError={errors?.teamID?.hasError} + runValidationTasks={async () => + await runValidationTasks(\\"teamID\\", currentTeamIDValue) + } + errorMessage={errors?.teamID?.errorMessage} + getBadgeText={(value) => + value + ? getDisplayValue.teamID( + teamIDRecords.find((r) => r.id === value) ?? + selectedTeamIDRecords.find((r) => r.id === value) + ) + : \\"\\" + } + setFieldValue={(value) => { + setCurrentTeamIDDisplayValue( + value + ? getDisplayValue.teamID( + teamIDRecords.find((r) => r.id === value) ?? + selectedTeamIDRecords.find((r) => r.id === value) + ) + : \\"\\" + ); + setCurrentTeamIDValue(value); + const selectedRecord = teamIDRecords.find((r) => r.id === value); + if (selectedRecord) { + setSelectedTeamIDRecords([selectedRecord]); + } + }} + inputFieldRef={teamIDRef} + defaultFieldValue={\\"\\"} + > + + arr.findIndex((member) => member?.id === r?.id) === i + ) + .map((r) => ({ + id: r?.id, + label: getDisplayValue.teamID?.(r), + }))} + isLoading={teamIDLoading} + onSelect={({ id, label }) => { + setCurrentTeamIDValue(id); + setCurrentTeamIDDisplayValue(label); + runValidationTasks(\\"teamID\\", label); + }} + onClear={() => { + setCurrentTeamIDDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchTeamIDRecords(value); + if (errors.teamID?.hasError) { + runValidationTasks(\\"teamID\\", value); + } + setCurrentTeamIDDisplayValue(value); + setCurrentTeamIDValue(undefined); + }} + onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} + errorMessage={errors.teamID?.errorMessage} + hasError={errors.teamID?.hasError} + ref={teamIDRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"teamID\\")} + > + + { + let value = items[0]; + if (onChange) { + const modelFields = { + name, + teamID, + Team: value, + }; + const result = onChange(modelFields); + value = result?.Team ?? value; + } + setTeam(value); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(\\"\\"); + }} + currentFieldValue={currentTeamValue} + label={\\"Team Label\\"} + items={Team ? [Team] : []} + hasError={errors?.Team?.hasError} + runValidationTasks={async () => + await runValidationTasks(\\"Team\\", currentTeamValue) + } + errorMessage={errors?.Team?.errorMessage} + getBadgeText={getDisplayValue.Team} + setFieldValue={(model) => { + setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); + setCurrentTeamValue(model); + }} + inputFieldRef={TeamRef} + defaultFieldValue={\\"\\"} + > + !TeamIdSet.has(getIDValue.Team?.(r)) + ).map((r) => ({ + id: getIDValue.Team?.(r), + label: getDisplayValue.Team?.(r), + }))} + isLoading={TeamLoading} + onSelect={({ id, label }) => { + setCurrentTeamValue( + TeamRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentTeamDisplayValue(label); + runValidationTasks(\\"Team\\", label); + }} + onClear={() => { + setCurrentTeamDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchTeamRecords(value); + if (errors.Team?.hasError) { + runValidationTasks(\\"Team\\", value); + } + setCurrentTeamDisplayValue(value); + setCurrentTeamValue(undefined); + }} + onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} + errorMessage={errors.Team?.errorMessage} + hasError={errors.Team?.hasError} + ref={TeamRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"Team\\")} + > + + + ); +} +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 2`] = ` +"import * as React from \\"react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Team } from \\"../API\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type MyMemberFormInputValues = { + name?: string; + teamID?: string; + Team?: Team; +}; +export declare type MyMemberFormValidationValues = { + name?: ValidationFunction; + teamID?: ValidationFunction; + Team?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type MyMemberFormOverridesProps = { + MyMemberFormGrid?: PrimitiveOverrideProps; + name?: PrimitiveOverrideProps; + teamID?: PrimitiveOverrideProps; + Team?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type MyMemberFormProps = React.PropsWithChildren<{ + overrides?: MyMemberFormOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onSuccess?: (fields: MyMemberFormInputValues) => void; + onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onValidate?: MyMemberFormValidationValues; +} & React.CSSProperties>; +export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form with composite primary key 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Tag } from \\"../API\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { API } from \\"aws-amplify\\"; +import { listTags } from \\"../graphql/queries\\"; +import { createMovie, createMovieTags } from \\"../graphql/mutations\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + runValidationTasks, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + const { hasError } = runValidationTasks(); + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function MovieCreateForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + movieKey: \\"\\", + title: \\"\\", + genre: \\"\\", + rating: \\"\\", + tags: [], + }; + const [movieKey, setMovieKey] = React.useState(initialValues.movieKey); + const [title, setTitle] = React.useState(initialValues.title); + const [genre, setGenre] = React.useState(initialValues.genre); + const [rating, setRating] = React.useState(initialValues.rating); + const [tags, setTags] = React.useState(initialValues.tags); + const [tagsLoading, setTagsLoading] = React.useState(false); + const [tagsRecords, setTagsRecords] = React.useState([]); + const autocompleteLength = 10; + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setMovieKey(initialValues.movieKey); + setTitle(initialValues.title); + setGenre(initialValues.genre); + setRating(initialValues.rating); + setTags(initialValues.tags); + setCurrentTagsValue(undefined); + setCurrentTagsDisplayValue(\\"\\"); + setErrors({}); + }; + const [currentTagsDisplayValue, setCurrentTagsDisplayValue] = + React.useState(\\"\\"); + const [currentTagsValue, setCurrentTagsValue] = React.useState(undefined); + const tagsRef = React.createRef(); + const getIDValue = { + tags: (r) => JSON.stringify({ id: r?.id }), + }; + const tagsIdSet = new Set( + Array.isArray(tags) + ? tags.map((r) => getIDValue.tags?.(r)) + : getIDValue.tags?.(tags) + ); + const getDisplayValue = { + tags: (r) => \`\${r?.label ? r?.label + \\" - \\" : \\"\\"}\${r?.id}\`, + }; + const validations = { + movieKey: [{ type: \\"Required\\" }], + title: [{ type: \\"Required\\" }], + genre: [{ type: \\"Required\\" }], + rating: [], + tags: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + const fetchTagsRecords = async (value) => { + setTagsLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ label: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTags.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listTags?.items; + var loaded = result.filter( + (item) => !tagsIdSet.has(getIDValue.tags?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setTagsRecords(newOptions.slice(0, autocompleteLength)); + setTagsLoading(false); + }; + React.useEffect(() => { + fetchTagsRecords(\\"\\"); + }, []); + return ( + { + event.preventDefault(); + let modelFields = { + movieKey, + title, + genre, + rating, + tags, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value === \\"\\") { + modelFields[key] = null; + } + }); + const modelFieldsToSave = { + movieKey: modelFields.movieKey, + title: modelFields.title, + genre: modelFields.genre, + rating: modelFields.rating, + }; + const movie = ( + await API.graphql({ + query: createMovie.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFieldsToSave, + }, + }, + }) + )?.data?.createMovie; + const promises = []; + promises.push( + ...tags.reduce((promises, tag) => { + promises.push( + API.graphql({ + query: createMovieTags.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + movieMovieKey: movie.movieKey, + movietitle: movie.title, + moviegenre: movie.genre, + tagId: Tag.id, + }, + }, + }) + ); + return promises; + }, []) + ); + await Promise.all(promises); + if (onSuccess) { + onSuccess(modelFields); } - if (errors.username?.hasError) { - runValidationTasks(\\"username\\", value); + if (clearOnSuccess) { + resetStateValues(); } - setUsername(value); - }} - onBlur={() => runValidationTasks(\\"username\\", username)} - errorMessage={errors.username?.errorMessage} - hasError={errors.username?.hasError} - {...getOverrideProps(overrides, \\"username\\")} - > + } catch (err) { + if (onError) { + const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); + onError(modelFields, messages); + } + } + }} + {...getOverrideProps(overrides, \\"MovieCreateForm\\")} + {...rest} + > { let { value } = e.target; if (onChange) { const modelFields = { - caption, - username, - post_url: value, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, + movieKey: value, + title, + genre, + rating, + tags, }; const result = onChange(modelFields); - value = result?.post_url ?? value; + value = result?.movieKey ?? value; } - if (errors.post_url?.hasError) { - runValidationTasks(\\"post_url\\", value); + if (errors.movieKey?.hasError) { + runValidationTasks(\\"movieKey\\", value); } - setPost_url(value); + setMovieKey(value); }} - onBlur={() => runValidationTasks(\\"post_url\\", post_url)} - errorMessage={errors.post_url?.errorMessage} - hasError={errors.post_url?.hasError} - {...getOverrideProps(overrides, \\"post_url\\")} + onBlur={() => runValidationTasks(\\"movieKey\\", movieKey)} + errorMessage={errors.movieKey?.errorMessage} + hasError={errors.movieKey?.hasError} + {...getOverrideProps(overrides, \\"movieKey\\")} > - { let { value } = e.target; - if (onChange) { - const modelFields = { - caption, - username, - post_url, - metadata: value, - profile_url, - nonModelField, - nonModelFieldArray, + if (onChange) { + const modelFields = { + movieKey, + title: value, + genre, + rating, + tags, }; const result = onChange(modelFields); - value = result?.metadata ?? value; + value = result?.title ?? value; } - if (errors.metadata?.hasError) { - runValidationTasks(\\"metadata\\", value); + if (errors.title?.hasError) { + runValidationTasks(\\"title\\", value); } - setMetadata(value); + setTitle(value); }} - onBlur={() => runValidationTasks(\\"metadata\\", metadata)} - errorMessage={errors.metadata?.errorMessage} - hasError={errors.metadata?.hasError} - {...getOverrideProps(overrides, \\"metadata\\")} - > + onBlur={() => runValidationTasks(\\"title\\", title)} + errorMessage={errors.title?.errorMessage} + hasError={errors.title?.hasError} + {...getOverrideProps(overrides, \\"title\\")} + > { let { value } = e.target; if (onChange) { const modelFields = { - caption, - username, - post_url, - metadata, - profile_url: value, - nonModelField, - nonModelFieldArray, + movieKey, + title, + genre: value, + rating, + tags, }; const result = onChange(modelFields); - value = result?.profile_url ?? value; + value = result?.genre ?? value; } - if (errors.profile_url?.hasError) { - runValidationTasks(\\"profile_url\\", value); + if (errors.genre?.hasError) { + runValidationTasks(\\"genre\\", value); } - setProfile_url(value); + setGenre(value); }} - onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} - errorMessage={errors.profile_url?.errorMessage} - hasError={errors.profile_url?.hasError} - {...getOverrideProps(overrides, \\"profile_url\\")} + onBlur={() => runValidationTasks(\\"genre\\", genre)} + errorMessage={errors.genre?.errorMessage} + hasError={errors.genre?.hasError} + {...getOverrideProps(overrides, \\"genre\\")} > - { let { value } = e.target; if (onChange) { const modelFields = { - caption, - username, - post_url, - metadata, - profile_url, - nonModelField: value, - nonModelFieldArray, + movieKey, + title, + genre, + rating: value, + tags, }; const result = onChange(modelFields); - value = result?.nonModelField ?? value; + value = result?.rating ?? value; } - if (errors.nonModelField?.hasError) { - runValidationTasks(\\"nonModelField\\", value); + if (errors.rating?.hasError) { + runValidationTasks(\\"rating\\", value); } - setNonModelField(value); + setRating(value); }} - onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} - errorMessage={errors.nonModelField?.errorMessage} - hasError={errors.nonModelField?.hasError} - {...getOverrideProps(overrides, \\"nonModelField\\")} - > + onBlur={() => runValidationTasks(\\"rating\\", rating)} + errorMessage={errors.rating?.errorMessage} + hasError={errors.rating?.hasError} + {...getOverrideProps(overrides, \\"rating\\")} + > { let values = items; if (onChange) { const modelFields = { - caption, - username, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray: values, + movieKey, + title, + genre, + rating, + tags: values, }; const result = onChange(modelFields); - values = result?.nonModelFieldArray ?? values; + values = result?.tags ?? values; } - setNonModelFieldArray(values); - setCurrentNonModelFieldArrayValue(\\"\\"); + setTags(values); + setCurrentTagsValue(undefined); + setCurrentTagsDisplayValue(\\"\\"); }} - currentFieldValue={currentNonModelFieldArrayValue} - label={\\"Non model field array\\"} - items={nonModelFieldArray} - hasError={errors?.nonModelFieldArray?.hasError} + currentFieldValue={currentTagsValue} + label={\\"Tags\\"} + items={tags} + hasError={errors?.tags?.hasError} runValidationTasks={async () => - await runValidationTasks( - \\"nonModelFieldArray\\", - currentNonModelFieldArrayValue - ) + await runValidationTasks(\\"tags\\", currentTagsValue) } - errorMessage={errors?.nonModelFieldArray?.errorMessage} - setFieldValue={setCurrentNonModelFieldArrayValue} - inputFieldRef={nonModelFieldArrayRef} + errorMessage={errors?.tags?.errorMessage} + getBadgeText={getDisplayValue.tags} + setFieldValue={(model) => { + setCurrentTagsDisplayValue(model ? getDisplayValue.tags(model) : \\"\\"); + setCurrentTagsValue(model); + }} + inputFieldRef={tagsRef} defaultFieldValue={\\"\\"} > - ({ + id: getIDValue.tags?.(r), + label: getDisplayValue.tags?.(r), + }))} + isLoading={tagsLoading} + onSelect={({ id, label }) => { + setCurrentTagsValue( + tagsRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentTagsDisplayValue(label); + runValidationTasks(\\"tags\\", label); + }} + onClear={() => { + setCurrentTagsDisplayValue(\\"\\"); + }} onChange={(e) => { let { value } = e.target; - if (errors.nonModelFieldArray?.hasError) { - runValidationTasks(\\"nonModelFieldArray\\", value); + fetchTagsRecords(value); + if (errors.tags?.hasError) { + runValidationTasks(\\"tags\\", value); } - setCurrentNonModelFieldArrayValue(value); + setCurrentTagsDisplayValue(value); + setCurrentTagsValue(undefined); }} - onBlur={() => - runValidationTasks( - \\"nonModelFieldArray\\", - currentNonModelFieldArrayValue - ) - } - errorMessage={errors.nonModelFieldArray?.errorMessage} - hasError={errors.nonModelFieldArray?.hasError} - ref={nonModelFieldArrayRef} + onBlur={() => runValidationTasks(\\"tags\\", currentTagsDisplayValue)} + errorMessage={errors.tags?.errorMessage} + hasError={errors.tags?.hasError} + ref={tagsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} - > + {...getOverrideProps(overrides, \\"tags\\")} + > + + + + + + ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with composite primary key 2`] = ` "import * as React from \\"react\\"; -import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Tag } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type MyPostFormInputValues = { - caption?: string; - username?: string; - post_url?: string; - metadata?: string; - profile_url?: string; - nonModelField?: string; - nonModelFieldArray?: string[]; +export declare type MovieCreateFormInputValues = { + movieKey?: string; + title?: string; + genre?: string; + rating?: string; + tags?: Tag[]; }; -export declare type MyPostFormValidationValues = { - caption?: ValidationFunction; - username?: ValidationFunction; - post_url?: ValidationFunction; - metadata?: ValidationFunction; - profile_url?: ValidationFunction; - nonModelField?: ValidationFunction; - nonModelFieldArray?: ValidationFunction; +export declare type MovieCreateFormValidationValues = { + movieKey?: ValidationFunction; + title?: ValidationFunction; + genre?: ValidationFunction; + rating?: ValidationFunction; + tags?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type MyPostFormOverridesProps = { - MyPostFormGrid?: PrimitiveOverrideProps; - caption?: PrimitiveOverrideProps; - username?: PrimitiveOverrideProps; - post_url?: PrimitiveOverrideProps; - metadata?: PrimitiveOverrideProps; - profile_url?: PrimitiveOverrideProps; - nonModelField?: PrimitiveOverrideProps; - nonModelFieldArray?: PrimitiveOverrideProps; +export declare type MovieCreateFormOverridesProps = { + MovieCreateFormGrid?: PrimitiveOverrideProps; + movieKey?: PrimitiveOverrideProps; + title?: PrimitiveOverrideProps; + genre?: PrimitiveOverrideProps; + rating?: PrimitiveOverrideProps; + tags?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type MyPostFormProps = React.PropsWithChildren<{ - overrides?: MyPostFormOverridesProps | undefined | null; +export declare type MovieCreateFormProps = React.PropsWithChildren<{ + overrides?: MovieCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onSuccess?: (fields: MyPostFormInputValues) => void; - onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onValidate?: MyPostFormValidationValues; + onSubmit?: (fields: MovieCreateFormInputValues) => MovieCreateFormInputValues; + onSuccess?: (fields: MovieCreateFormInputValues) => void; + onError?: (fields: MovieCreateFormInputValues, errorMessage: string) => void; + onChange?: (fields: MovieCreateFormInputValues) => MovieCreateFormInputValues; + onValidate?: MovieCreateFormValidationValues; } & React.CSSProperties>; -export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +export default function MovieCreateForm(props: MovieCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -1231,8 +3767,8 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listTeams } from \\"../graphql/queries\\"; -import { createMember } from \\"../graphql/mutations\\"; +import { listStudents } from \\"../graphql/queries\\"; +import { createSchool, updateStudent } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -1388,7 +3924,7 @@ function ArrayField({ ); } -export default function MyMemberForm(props) { +export default function SchoolCreateForm(props) { const { clearOnSuccess = true, onSuccess, @@ -1402,53 +3938,40 @@ export default function MyMemberForm(props) { } = props; const initialValues = { name: \\"\\", - teamID: undefined, - Team: undefined, + Students: [], }; const [name, setName] = React.useState(initialValues.name); - const [teamID, setTeamID] = React.useState(initialValues.teamID); - const [teamIDLoading, setTeamIDLoading] = React.useState(false); - const [teamIDRecords, setTeamIDRecords] = React.useState([]); - const [selectedTeamIDRecords, setSelectedTeamIDRecords] = React.useState([]); - const [Team, setTeam] = React.useState(initialValues.Team); - const [TeamLoading, setTeamLoading] = React.useState(false); - const [TeamRecords, setTeamRecords] = React.useState([]); + const [Students, setStudents] = React.useState(initialValues.Students); + const [StudentsLoading, setStudentsLoading] = React.useState(false); + const [StudentsRecords, setStudentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); - setTeamID(initialValues.teamID); - setCurrentTeamIDValue(undefined); - setCurrentTeamIDDisplayValue(\\"\\"); - setTeam(initialValues.Team); - setCurrentTeamValue(undefined); - setCurrentTeamDisplayValue(\\"\\"); + setStudents(initialValues.Students); + setCurrentStudentsValue(undefined); + setCurrentStudentsDisplayValue(\\"\\"); setErrors({}); }; - const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = - React.useState(\\"\\"); - const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); - const teamIDRef = React.createRef(); - const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = + const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = React.useState(\\"\\"); - const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); - const TeamRef = React.createRef(); + const [currentStudentsValue, setCurrentStudentsValue] = + React.useState(undefined); + const StudentsRef = React.createRef(); const getIDValue = { - Team: (r) => JSON.stringify({ id: r?.id }), + Students: (r) => JSON.stringify({ id: r?.id }), }; - const TeamIdSet = new Set( - Array.isArray(Team) - ? Team.map((r) => getIDValue.Team?.(r)) - : getIDValue.Team?.(Team) + const StudentsIdSet = new Set( + Array.isArray(Students) + ? Students.map((r) => getIDValue.Students?.(r)) + : getIDValue.Students?.(Students) ); const getDisplayValue = { - teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, - Team: (r) => r?.name, + Students: (r) => r?.name, }; const validations = { name: [], - teamID: [{ type: \\"Required\\" }], - Team: [], + Students: [], }; const runValidationTasks = async ( fieldName, @@ -1467,35 +3990,8 @@ export default function MyMemberForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchTeamIDRecords = async (value) => { - setTeamIDLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { - or: [{ name: { contains: value } }, { id: { contains: value } }], - }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listTeams.replaceAll(\\"__typename\\", \\"\\"), - variables, - }) - )?.data?.listTeams?.items; - var loaded = result.filter((item) => teamID !== item.id); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setTeamIDRecords(newOptions.slice(0, autocompleteLength)); - setTeamIDLoading(false); - }; - const fetchTeamRecords = async (value) => { - setTeamLoading(true); + const fetchStudentsRecords = async (value) => { + setStudentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { @@ -1508,22 +4004,21 @@ export default function MyMemberForm(props) { } const result = ( await API.graphql({ - query: listTeams.replaceAll(\\"__typename\\", \\"\\"), + query: listStudents.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listTeams?.items; + )?.data?.listStudents?.items; var loaded = result.filter( - (item) => !TeamIdSet.has(getIDValue.Team?.(item)) + (item) => !StudentsIdSet.has(getIDValue.Students?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setTeamRecords(newOptions.slice(0, autocompleteLength)); - setTeamLoading(false); + setStudentsRecords(newOptions.slice(0, autocompleteLength)); + setStudentsLoading(false); }; React.useEffect(() => { - fetchTeamIDRecords(\\"\\"); - fetchTeamRecords(\\"\\"); + fetchStudentsRecords(\\"\\"); }, []); return ( { @@ -1576,17 +4070,35 @@ export default function MyMemberForm(props) { }); const modelFieldsToSave = { name: modelFields.name, - teamID: modelFields.teamID, - teamMembersId: modelFields?.Team?.id, }; - await API.graphql({ - query: createMember.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - ...modelFieldsToSave, + const school = ( + await API.graphql({ + query: createSchool.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFieldsToSave, + }, }, - }, - }); + }) + )?.data?.createSchool; + const promises = []; + promises.push( + ...Students.reduce((promises, original) => { + promises.push( + API.graphql({ + query: updateStudent.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: original.id, + schoolID: school.id, + }, + }, + }) + ); + return promises; + }, []) + ); + await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -1600,43 +4112,9 @@ export default function MyMemberForm(props) { } } }} - {...getOverrideProps(overrides, \\"MyMemberForm\\")} + {...getOverrideProps(overrides, \\"SchoolCreateForm\\")} {...rest} > - - - - - - - runValidationTasks(\\"name\\", name)} - errorMessage={errors.name?.errorMessage} - hasError={errors.name?.hasError} - {...getOverrideProps(overrides, \\"name\\")} - > - { - let value = items[0]; - if (onChange) { - const modelFields = { - name, - teamID: value, - Team, - }; - const result = onChange(modelFields); - value = result?.teamID ?? value; - } - setTeamID(value); - setCurrentTeamIDValue(undefined); - }} - currentFieldValue={currentTeamIDValue} - label={\\"Team id\\"} - items={teamID ? [teamID] : []} - hasError={errors?.teamID?.hasError} - runValidationTasks={async () => - await runValidationTasks(\\"teamID\\", currentTeamIDValue) - } - errorMessage={errors?.teamID?.errorMessage} - getBadgeText={(value) => - value - ? getDisplayValue.teamID( - teamIDRecords.find((r) => r.id === value) ?? - selectedTeamIDRecords.find((r) => r.id === value) - ) - : \\"\\" - } - setFieldValue={(value) => { - setCurrentTeamIDDisplayValue( - value - ? getDisplayValue.teamID( - teamIDRecords.find((r) => r.id === value) ?? - selectedTeamIDRecords.find((r) => r.id === value) - ) - : \\"\\" - ); - setCurrentTeamIDValue(value); - const selectedRecord = teamIDRecords.find((r) => r.id === value); - if (selectedRecord) { - setSelectedTeamIDRecords([selectedRecord]); + runValidationTasks(\\"name\\", value); } + setName(value); }} - inputFieldRef={teamIDRef} - defaultFieldValue={\\"\\"} - > - - arr.findIndex((member) => member?.id === r?.id) === i - ) - .map((r) => ({ - id: r?.id, - label: getDisplayValue.teamID?.(r), - }))} - isLoading={teamIDLoading} - onSelect={({ id, label }) => { - setCurrentTeamIDValue(id); - setCurrentTeamIDDisplayValue(label); - runValidationTasks(\\"teamID\\", label); - }} - onClear={() => { - setCurrentTeamIDDisplayValue(\\"\\"); - }} - onChange={(e) => { - let { value } = e.target; - fetchTeamIDRecords(value); - if (errors.teamID?.hasError) { - runValidationTasks(\\"teamID\\", value); - } - setCurrentTeamIDDisplayValue(value); - setCurrentTeamIDValue(undefined); - }} - onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} - errorMessage={errors.teamID?.errorMessage} - hasError={errors.teamID?.hasError} - ref={teamIDRef} - labelHidden={true} - {...getOverrideProps(overrides, \\"teamID\\")} - > - + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} + > { - let value = items[0]; + let values = items; if (onChange) { const modelFields = { name, - teamID, - Team: value, + Students: values, }; const result = onChange(modelFields); - value = result?.Team ?? value; + values = result?.Students ?? values; } - setTeam(value); - setCurrentTeamValue(undefined); - setCurrentTeamDisplayValue(\\"\\"); + setStudents(values); + setCurrentStudentsValue(undefined); + setCurrentStudentsDisplayValue(\\"\\"); }} - currentFieldValue={currentTeamValue} - label={\\"Team Label\\"} - items={Team ? [Team] : []} - hasError={errors?.Team?.hasError} + currentFieldValue={currentStudentsValue} + label={\\"Students\\"} + items={Students} + hasError={errors?.Students?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"Team\\", currentTeamValue) + await runValidationTasks(\\"Students\\", currentStudentsValue) } - errorMessage={errors?.Team?.errorMessage} - getBadgeText={getDisplayValue.Team} + errorMessage={errors?.Students?.errorMessage} + getBadgeText={getDisplayValue.Students} setFieldValue={(model) => { - setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); - setCurrentTeamValue(model); + setCurrentStudentsDisplayValue( + model ? getDisplayValue.Students(model) : \\"\\" + ); + setCurrentStudentsValue(model); }} - inputFieldRef={TeamRef} + inputFieldRef={StudentsRef} defaultFieldValue={\\"\\"} > !TeamIdSet.has(getIDValue.Team?.(r)) + placeholder=\\"Search Student\\" + value={currentStudentsDisplayValue} + options={StudentsRecords.filter( + (r) => !StudentsIdSet.has(getIDValue.Students?.(r)) ).map((r) => ({ - id: getIDValue.Team?.(r), - label: getDisplayValue.Team?.(r), + id: getIDValue.Students?.(r), + label: getDisplayValue.Students?.(r), }))} - isLoading={TeamLoading} + isLoading={StudentsLoading} onSelect={({ id, label }) => { - setCurrentTeamValue( - TeamRecords.find((r) => + setCurrentStudentsValue( + StudentsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentTeamDisplayValue(label); - runValidationTasks(\\"Team\\", label); + setCurrentStudentsDisplayValue(label); + runValidationTasks(\\"Students\\", label); }} onClear={() => { - setCurrentTeamDisplayValue(\\"\\"); + setCurrentStudentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchTeamRecords(value); - if (errors.Team?.hasError) { - runValidationTasks(\\"Team\\", value); + fetchStudentsRecords(value); + if (errors.Students?.hasError) { + runValidationTasks(\\"Students\\", value); } - setCurrentTeamDisplayValue(value); - setCurrentTeamValue(undefined); + setCurrentStudentsDisplayValue(value); + setCurrentStudentsValue(undefined); }} - onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} - errorMessage={errors.Team?.errorMessage} - hasError={errors.Team?.hasError} - ref={TeamRef} + onBlur={() => + runValidationTasks(\\"Students\\", currentStudentsDisplayValue) + } + errorMessage={errors.Students?.errorMessage} + hasError={errors.Students?.hasError} + ref={StudentsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Team\\")} + {...getOverrideProps(overrides, \\"Students\\")} > + + + + + + + ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Team } from \\"../API\\"; +import { Student } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type MyMemberFormInputValues = { +export declare type SchoolCreateFormInputValues = { name?: string; - teamID?: string; - Team?: Team; + Students?: Student[]; }; -export declare type MyMemberFormValidationValues = { +export declare type SchoolCreateFormValidationValues = { name?: ValidationFunction; - teamID?: ValidationFunction; - Team?: ValidationFunction; + Students?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type MyMemberFormOverridesProps = { - MyMemberFormGrid?: PrimitiveOverrideProps; +export declare type SchoolCreateFormOverridesProps = { + SchoolCreateFormGrid?: PrimitiveOverrideProps; name?: PrimitiveOverrideProps; - teamID?: PrimitiveOverrideProps; - Team?: PrimitiveOverrideProps; + Students?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type MyMemberFormProps = React.PropsWithChildren<{ - overrides?: MyMemberFormOverridesProps | undefined | null; +export declare type SchoolCreateFormProps = React.PropsWithChildren<{ + overrides?: SchoolCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onSuccess?: (fields: MyMemberFormInputValues) => void; - onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; + onSuccess?: (fields: SchoolCreateFormInputValues) => void; + onError?: (fields: SchoolCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onValidate?: MyMemberFormValidationValues; + onChange?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; + onValidate?: SchoolCreateFormValidationValues; } & React.CSSProperties>; -export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; +export default function SchoolCreateForm(props: SchoolCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with composite primary key 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -1896,11 +4315,10 @@ import { useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Tag } from \\"../API\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listTags } from \\"../graphql/queries\\"; -import { createMovie, createMovieTags } from \\"../graphql/mutations\\"; +import { listAuthors } from \\"../graphql/queries\\"; +import { createBook } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -2056,64 +4474,58 @@ function ArrayField({ ); } -export default function MovieCreateForm(props) { +export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, + onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - movieKey: \\"\\", - title: \\"\\", - genre: \\"\\", - rating: \\"\\", - tags: [], + name: \\"\\", + primaryAuthor: undefined, }; - const [movieKey, setMovieKey] = React.useState(initialValues.movieKey); - const [title, setTitle] = React.useState(initialValues.title); - const [genre, setGenre] = React.useState(initialValues.genre); - const [rating, setRating] = React.useState(initialValues.rating); - const [tags, setTags] = React.useState(initialValues.tags); - const [tagsLoading, setTagsLoading] = React.useState(false); - const [tagsRecords, setTagsRecords] = React.useState([]); + const [name, setName] = React.useState(initialValues.name); + const [primaryAuthor, setPrimaryAuthor] = React.useState( + initialValues.primaryAuthor + ); + const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); + const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setMovieKey(initialValues.movieKey); - setTitle(initialValues.title); - setGenre(initialValues.genre); - setRating(initialValues.rating); - setTags(initialValues.tags); - setCurrentTagsValue(undefined); - setCurrentTagsDisplayValue(\\"\\"); + setName(initialValues.name); + setPrimaryAuthor(initialValues.primaryAuthor); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); setErrors({}); }; - const [currentTagsDisplayValue, setCurrentTagsDisplayValue] = - React.useState(\\"\\"); - const [currentTagsValue, setCurrentTagsValue] = React.useState(undefined); - const tagsRef = React.createRef(); + const [ + currentPrimaryAuthorDisplayValue, + setCurrentPrimaryAuthorDisplayValue, + ] = React.useState(\\"\\"); + const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = + React.useState(undefined); + const primaryAuthorRef = React.createRef(); const getIDValue = { - tags: (r) => JSON.stringify({ id: r?.id }), + primaryAuthor: (r) => JSON.stringify({ id: r?.id }), }; - const tagsIdSet = new Set( - Array.isArray(tags) - ? tags.map((r) => getIDValue.tags?.(r)) - : getIDValue.tags?.(tags) + const primaryAuthorIdSet = new Set( + Array.isArray(primaryAuthor) + ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) + : getIDValue.primaryAuthor?.(primaryAuthor) ); const getDisplayValue = { - tags: (r) => \`\${r?.label ? r?.label + \\" - \\" : \\"\\"}\${r?.id}\`, + primaryAuthor: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { - movieKey: [{ type: \\"Required\\" }], - title: [{ type: \\"Required\\" }], - genre: [{ type: \\"Required\\" }], - rating: [], - tags: [], + name: [], + primaryAuthor: [], }; const runValidationTasks = async ( fieldName, @@ -2132,15 +4544,15 @@ export default function MovieCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchTagsRecords = async (value) => { - setTagsLoading(true); + const fetchPrimaryAuthorRecords = async (value) => { + setPrimaryAuthorLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { - or: [{ label: { contains: value } }, { id: { contains: value } }], + or: [{ name: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { @@ -2148,21 +4560,21 @@ export default function MovieCreateForm(props) { } const result = ( await API.graphql({ - query: listTags.replaceAll(\\"__typename\\", \\"\\"), + query: listAuthors.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listTags?.items; + )?.data?.listAuthors?.items; var loaded = result.filter( - (item) => !tagsIdSet.has(getIDValue.tags?.(item)) + (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setTagsRecords(newOptions.slice(0, autocompleteLength)); - setTagsLoading(false); + setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); + setPrimaryAuthorLoading(false); }; React.useEffect(() => { - fetchTagsRecords(\\"\\"); + fetchPrimaryAuthorRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { - movieKey, - title, - genre, - rating, - tags, + name, + primaryAuthor, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -2216,41 +4625,17 @@ export default function MovieCreateForm(props) { } }); const modelFieldsToSave = { - movieKey: modelFields.movieKey, - title: modelFields.title, - genre: modelFields.genre, - rating: modelFields.rating, + name: modelFields.name, + authorId: modelFields?.primaryAuthor?.id, }; - const movie = ( - await API.graphql({ - query: createMovie.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - ...modelFieldsToSave, - }, + await API.graphql({ + query: createBook.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFieldsToSave, }, - }) - )?.data?.createMovie; - const promises = []; - promises.push( - ...tags.reduce((promises, tag) => { - promises.push( - API.graphql({ - query: createMovieTags.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - movieMovieKey: movie.movieKey, - movietitle: movie.title, - moviegenre: movie.genre, - tagId: Tag.id, - }, - }, - }) - ); - return promises; - }, []) - ); - await Promise.all(promises); + }, + }); if (onSuccess) { onSuccess(modelFields); } @@ -2264,277 +4649,199 @@ export default function MovieCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"MovieCreateForm\\")} + {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > + + + + + + + { - let { value } = e.target; - if (onChange) { - const modelFields = { - movieKey: value, - title, - genre, - rating, - tags, - }; - const result = onChange(modelFields); - value = result?.movieKey ?? value; - } - if (errors.movieKey?.hasError) { - runValidationTasks(\\"movieKey\\", value); - } - setMovieKey(value); - }} - onBlur={() => runValidationTasks(\\"movieKey\\", movieKey)} - errorMessage={errors.movieKey?.errorMessage} - hasError={errors.movieKey?.hasError} - {...getOverrideProps(overrides, \\"movieKey\\")} - > - { - let { value } = e.target; - if (onChange) { - const modelFields = { - movieKey, - title: value, - genre, - rating, - tags, - }; - const result = onChange(modelFields); - value = result?.title ?? value; - } - if (errors.title?.hasError) { - runValidationTasks(\\"title\\", value); - } - setTitle(value); - }} - onBlur={() => runValidationTasks(\\"title\\", title)} - errorMessage={errors.title?.errorMessage} - hasError={errors.title?.hasError} - {...getOverrideProps(overrides, \\"title\\")} - > - { - let { value } = e.target; - if (onChange) { - const modelFields = { - movieKey, - title, - genre: value, - rating, - tags, - }; - const result = onChange(modelFields); - value = result?.genre ?? value; - } - if (errors.genre?.hasError) { - runValidationTasks(\\"genre\\", value); - } - setGenre(value); - }} - onBlur={() => runValidationTasks(\\"genre\\", genre)} - errorMessage={errors.genre?.errorMessage} - hasError={errors.genre?.hasError} - {...getOverrideProps(overrides, \\"genre\\")} - > - { let { value } = e.target; if (onChange) { const modelFields = { - movieKey, - title, - genre, - rating: value, - tags, + name: value, + primaryAuthor, }; const result = onChange(modelFields); - value = result?.rating ?? value; + value = result?.name ?? value; } - if (errors.rating?.hasError) { - runValidationTasks(\\"rating\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setRating(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"rating\\", rating)} - errorMessage={errors.rating?.errorMessage} - hasError={errors.rating?.hasError} - {...getOverrideProps(overrides, \\"rating\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} > { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { - movieKey, - title, - genre, - rating, - tags: values, + name, + primaryAuthor: value, }; const result = onChange(modelFields); - values = result?.tags ?? values; + value = result?.primaryAuthor ?? value; } - setTags(values); - setCurrentTagsValue(undefined); - setCurrentTagsDisplayValue(\\"\\"); - }} - currentFieldValue={currentTagsValue} - label={\\"Tags\\"} - items={tags} - hasError={errors?.tags?.hasError} + setPrimaryAuthor(value); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); + }} + currentFieldValue={currentPrimaryAuthorValue} + label={\\"Primary author\\"} + items={primaryAuthor ? [primaryAuthor] : []} + hasError={errors?.primaryAuthor?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"tags\\", currentTagsValue) + await runValidationTasks(\\"primaryAuthor\\", currentPrimaryAuthorValue) } - errorMessage={errors?.tags?.errorMessage} - getBadgeText={getDisplayValue.tags} + errorMessage={errors?.primaryAuthor?.errorMessage} + getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => { - setCurrentTagsDisplayValue(model ? getDisplayValue.tags(model) : \\"\\"); - setCurrentTagsValue(model); + setCurrentPrimaryAuthorDisplayValue( + model ? getDisplayValue.primaryAuthor(model) : \\"\\" + ); + setCurrentPrimaryAuthorValue(model); }} - inputFieldRef={tagsRef} + inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > ({ - id: getIDValue.tags?.(r), - label: getDisplayValue.tags?.(r), - }))} - isLoading={tagsLoading} + placeholder=\\"Search Author\\" + value={currentPrimaryAuthorDisplayValue} + options={primaryAuthorRecords + .filter( + (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) + ) + .map((r) => ({ + id: getIDValue.primaryAuthor?.(r), + label: getDisplayValue.primaryAuthor?.(r), + }))} + isLoading={primaryAuthorLoading} onSelect={({ id, label }) => { - setCurrentTagsValue( - tagsRecords.find((r) => + setCurrentPrimaryAuthorValue( + primaryAuthorRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentTagsDisplayValue(label); - runValidationTasks(\\"tags\\", label); + setCurrentPrimaryAuthorDisplayValue(label); + runValidationTasks(\\"primaryAuthor\\", label); }} onClear={() => { - setCurrentTagsDisplayValue(\\"\\"); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchTagsRecords(value); - if (errors.tags?.hasError) { - runValidationTasks(\\"tags\\", value); + fetchPrimaryAuthorRecords(value); + if (errors.primaryAuthor?.hasError) { + runValidationTasks(\\"primaryAuthor\\", value); } - setCurrentTagsDisplayValue(value); - setCurrentTagsValue(undefined); + setCurrentPrimaryAuthorDisplayValue(value); + setCurrentPrimaryAuthorValue(undefined); }} - onBlur={() => runValidationTasks(\\"tags\\", currentTagsDisplayValue)} - errorMessage={errors.tags?.errorMessage} - hasError={errors.tags?.hasError} - ref={tagsRef} + onBlur={() => + runValidationTasks( + \\"primaryAuthor\\", + currentPrimaryAuthorDisplayValue + ) + } + errorMessage={errors.primaryAuthor?.errorMessage} + hasError={errors.primaryAuthor?.hasError} + ref={primaryAuthorRef} labelHidden={true} - {...getOverrideProps(overrides, \\"tags\\")} + {...getOverrideProps(overrides, \\"primaryAuthor\\")} > - - - - - - ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with composite primary key 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Tag } from \\"../API\\"; +import { Author } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type MovieCreateFormInputValues = { - movieKey?: string; - title?: string; - genre?: string; - rating?: string; - tags?: Tag[]; +export declare type BookCreateFormInputValues = { + name?: string; + primaryAuthor?: Author; }; -export declare type MovieCreateFormValidationValues = { - movieKey?: ValidationFunction; - title?: ValidationFunction; - genre?: ValidationFunction; - rating?: ValidationFunction; - tags?: ValidationFunction; +export declare type BookCreateFormValidationValues = { + name?: ValidationFunction; + primaryAuthor?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type MovieCreateFormOverridesProps = { - MovieCreateFormGrid?: PrimitiveOverrideProps; - movieKey?: PrimitiveOverrideProps; - title?: PrimitiveOverrideProps; - genre?: PrimitiveOverrideProps; - rating?: PrimitiveOverrideProps; - tags?: PrimitiveOverrideProps; +export declare type BookCreateFormOverridesProps = { + BookCreateFormGrid?: PrimitiveOverrideProps; + name?: PrimitiveOverrideProps; + primaryAuthor?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type MovieCreateFormProps = React.PropsWithChildren<{ - overrides?: MovieCreateFormOverridesProps | undefined | null; +export declare type BookCreateFormProps = React.PropsWithChildren<{ + overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: MovieCreateFormInputValues) => MovieCreateFormInputValues; - onSuccess?: (fields: MovieCreateFormInputValues) => void; - onError?: (fields: MovieCreateFormInputValues, errorMessage: string) => void; - onChange?: (fields: MovieCreateFormInputValues) => MovieCreateFormInputValues; - onValidate?: MovieCreateFormValidationValues; + onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onSuccess?: (fields: BookCreateFormInputValues) => void; + onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; -export default function MovieCreateForm(props: MovieCreateFormProps): React.ReactElement; +export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with id field instead of belongsTo 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -2553,8 +4860,8 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listStudents } from \\"../graphql/queries\\"; -import { createSchool, updateStudent } from \\"../graphql/mutations\\"; +import { listPosts } from \\"../graphql/queries\\"; +import { createComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -2710,54 +5017,45 @@ function ArrayField({ ); } -export default function SchoolCreateForm(props) { +export default function CommentCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - name: \\"\\", - Students: [], + content: \\"\\", + postID: undefined, }; - const [name, setName] = React.useState(initialValues.name); - const [Students, setStudents] = React.useState(initialValues.Students); - const [StudentsLoading, setStudentsLoading] = React.useState(false); - const [StudentsRecords, setStudentsRecords] = React.useState([]); + const [content, setContent] = React.useState(initialValues.content); + const [postID, setPostID] = React.useState(initialValues.postID); + const [postIDLoading, setPostIDLoading] = React.useState(false); + const [postIDRecords, setPostIDRecords] = React.useState([]); + const [selectedPostIDRecords, setSelectedPostIDRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setName(initialValues.name); - setStudents(initialValues.Students); - setCurrentStudentsValue(undefined); - setCurrentStudentsDisplayValue(\\"\\"); + setContent(initialValues.content); + setPostID(initialValues.postID); + setCurrentPostIDValue(undefined); + setCurrentPostIDDisplayValue(\\"\\"); setErrors({}); }; - const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = + const [currentPostIDDisplayValue, setCurrentPostIDDisplayValue] = React.useState(\\"\\"); - const [currentStudentsValue, setCurrentStudentsValue] = - React.useState(undefined); - const StudentsRef = React.createRef(); - const getIDValue = { - Students: (r) => JSON.stringify({ id: r?.id }), - }; - const StudentsIdSet = new Set( - Array.isArray(Students) - ? Students.map((r) => getIDValue.Students?.(r)) - : getIDValue.Students?.(Students) - ); + const [currentPostIDValue, setCurrentPostIDValue] = React.useState(undefined); + const postIDRef = React.createRef(); const getDisplayValue = { - Students: (r) => r?.name, + postID: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { - name: [], - Students: [], + content: [{ type: \\"Required\\" }], + postID: [], }; const runValidationTasks = async ( fieldName, @@ -2776,35 +5074,35 @@ export default function SchoolCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchStudentsRecords = async (value) => { - setStudentsLoading(true); + const fetchPostIDRecords = async (value) => { + setPostIDLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { or: [{ name: { contains: value } }] }, + filter: { + or: [{ title: { contains: value } }, { id: { contains: value } }], + }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listStudents.replaceAll(\\"__typename\\", \\"\\"), + query: listPosts.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listStudents?.items; - var loaded = result.filter( - (item) => !StudentsIdSet.has(getIDValue.Students?.(item)) - ); + )?.data?.listPosts?.items; + var loaded = result.filter((item) => postID !== item.id); newOptions.push(...loaded); newNext = result.nextToken; } - setStudentsRecords(newOptions.slice(0, autocompleteLength)); - setStudentsLoading(false); + setPostIDRecords(newOptions.slice(0, autocompleteLength)); + setPostIDLoading(false); }; React.useEffect(() => { - fetchStudentsRecords(\\"\\"); + fetchPostIDRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { - name, - Students, + content, + postID, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks( - fieldName, - item, - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, item) ) ); return promises; } promises.push( - runValidationTasks( - fieldName, - modelFields[fieldName], - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) @@ -2854,37 +5144,14 @@ export default function SchoolCreateForm(props) { modelFields[key] = null; } }); - const modelFieldsToSave = { - name: modelFields.name, - }; - const school = ( - await API.graphql({ - query: createSchool.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - ...modelFieldsToSave, - }, + await API.graphql({ + query: createComment.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + ...modelFields, }, - }) - )?.data?.createSchool; - const promises = []; - promises.push( - ...Students.reduce((promises, original) => { - promises.push( - API.graphql({ - query: updateStudent.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - id: original.id, - schoolID: school.id, - }, - }, - }) - ); - return promises; - }, []) - ); - await Promise.all(promises); + }, + }); if (onSuccess) { onSuccess(modelFields); } @@ -2898,111 +5165,122 @@ export default function SchoolCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"SchoolCreateForm\\")} + {...getOverrideProps(overrides, \\"CommentCreateForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - Students, + content: value, + postID, }; const result = onChange(modelFields); - value = result?.name ?? value; + value = result?.content ?? value; } - if (errors.name?.hasError) { - runValidationTasks(\\"name\\", value); + if (errors.content?.hasError) { + runValidationTasks(\\"content\\", value); } - setName(value); + setContent(value); }} - onBlur={() => runValidationTasks(\\"name\\", name)} - errorMessage={errors.name?.errorMessage} - hasError={errors.name?.hasError} - {...getOverrideProps(overrides, \\"name\\")} + onBlur={() => runValidationTasks(\\"content\\", content)} + errorMessage={errors.content?.errorMessage} + hasError={errors.content?.hasError} + {...getOverrideProps(overrides, \\"content\\")} > { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { - name, - Students: values, + content, + postID: value, }; const result = onChange(modelFields); - values = result?.Students ?? values; + value = result?.postID ?? value; } - setStudents(values); - setCurrentStudentsValue(undefined); - setCurrentStudentsDisplayValue(\\"\\"); + setPostID(value); + setCurrentPostIDValue(undefined); }} - currentFieldValue={currentStudentsValue} - label={\\"Students\\"} - items={Students} - hasError={errors?.Students?.hasError} + currentFieldValue={currentPostIDValue} + label={\\"Post id\\"} + items={postID ? [postID] : []} + hasError={errors?.postID?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"Students\\", currentStudentsValue) + await runValidationTasks(\\"postID\\", currentPostIDValue) } - errorMessage={errors?.Students?.errorMessage} - getBadgeText={getDisplayValue.Students} - setFieldValue={(model) => { - setCurrentStudentsDisplayValue( - model ? getDisplayValue.Students(model) : \\"\\" + errorMessage={errors?.postID?.errorMessage} + getBadgeText={(value) => + value + ? getDisplayValue.postID( + postIDRecords.find((r) => r.id === value) ?? + selectedPostIDRecords.find((r) => r.id === value) + ) + : \\"\\" + } + setFieldValue={(value) => { + setCurrentPostIDDisplayValue( + value + ? getDisplayValue.postID( + postIDRecords.find((r) => r.id === value) ?? + selectedPostIDRecords.find((r) => r.id === value) + ) + : \\"\\" ); - setCurrentStudentsValue(model); + setCurrentPostIDValue(value); + const selectedRecord = postIDRecords.find((r) => r.id === value); + if (selectedRecord) { + setSelectedPostIDRecords([selectedRecord]); + } }} - inputFieldRef={StudentsRef} + inputFieldRef={postIDRef} defaultFieldValue={\\"\\"} > !StudentsIdSet.has(getIDValue.Students?.(r)) - ).map((r) => ({ - id: getIDValue.Students?.(r), - label: getDisplayValue.Students?.(r), - }))} - isLoading={StudentsLoading} + placeholder=\\"Search Post\\" + value={currentPostIDDisplayValue} + options={postIDRecords + .filter( + (r, i, arr) => + arr.findIndex((member) => member?.id === r?.id) === i + ) + .map((r) => ({ + id: r?.id, + label: getDisplayValue.postID?.(r), + }))} + isLoading={postIDLoading} onSelect={({ id, label }) => { - setCurrentStudentsValue( - StudentsRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentStudentsDisplayValue(label); - runValidationTasks(\\"Students\\", label); + setCurrentPostIDValue(id); + setCurrentPostIDDisplayValue(label); + runValidationTasks(\\"postID\\", label); }} onClear={() => { - setCurrentStudentsDisplayValue(\\"\\"); + setCurrentPostIDDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchStudentsRecords(value); - if (errors.Students?.hasError) { - runValidationTasks(\\"Students\\", value); + fetchPostIDRecords(value); + if (errors.postID?.hasError) { + runValidationTasks(\\"postID\\", value); } - setCurrentStudentsDisplayValue(value); - setCurrentStudentsValue(undefined); + setCurrentPostIDDisplayValue(value); + setCurrentPostIDValue(undefined); }} - onBlur={() => - runValidationTasks(\\"Students\\", currentStudentsDisplayValue) - } - errorMessage={errors.Students?.errorMessage} - hasError={errors.Students?.hasError} - ref={StudentsRef} + onBlur={() => runValidationTasks(\\"postID\\", currentPostIDValue)} + errorMessage={errors.postID?.errorMessage} + hasError={errors.postID?.hasError} + ref={postIDRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Students\\")} + {...getOverrideProps(overrides, \\"postID\\")} > - - - - - - { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - primaryAuthor, + label: value, + Posts, + statuses, }; const result = onChange(modelFields); - value = result?.name ?? value; + value = result?.label ?? value; } - if (errors.name?.hasError) { - runValidationTasks(\\"name\\", value); + if (errors.label?.hasError) { + runValidationTasks(\\"label\\", value); } - setName(value); + setLabel(value); }} - onBlur={() => runValidationTasks(\\"name\\", name)} - errorMessage={errors.name?.errorMessage} - hasError={errors.name?.hasError} - {...getOverrideProps(overrides, \\"name\\")} + onBlur={() => runValidationTasks(\\"label\\", label)} + errorMessage={errors.label?.errorMessage} + hasError={errors.label?.hasError} + {...getOverrideProps(overrides, \\"label\\")} > { - let value = items[0]; + let values = items; if (onChange) { const modelFields = { - name, - primaryAuthor: value, + label, + Posts: values, + statuses, }; const result = onChange(modelFields); - value = result?.primaryAuthor ?? value; + values = result?.Posts ?? values; } - setPrimaryAuthor(value); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setPosts(values); + setCurrentPostsValue(undefined); + setCurrentPostsDisplayValue(\\"\\"); }} - currentFieldValue={currentPrimaryAuthorValue} - label={\\"Primary author\\"} - items={primaryAuthor ? [primaryAuthor] : []} - hasError={errors?.primaryAuthor?.hasError} + currentFieldValue={currentPostsValue} + label={\\"Posts\\"} + items={Posts} + hasError={errors?.Posts?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"primaryAuthor\\", currentPrimaryAuthorValue) + await runValidationTasks(\\"Posts\\", currentPostsValue) } - errorMessage={errors?.primaryAuthor?.errorMessage} - getBadgeText={getDisplayValue.primaryAuthor} + errorMessage={errors?.Posts?.errorMessage} + getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => { - setCurrentPrimaryAuthorDisplayValue( - model ? getDisplayValue.primaryAuthor(model) : \\"\\" + setCurrentPostsDisplayValue( + model ? getDisplayValue.Posts(model) : \\"\\" ); - setCurrentPrimaryAuthorValue(model); + setCurrentPostsValue(model); }} - inputFieldRef={primaryAuthorRef} + inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) - ) - .map((r) => ({ - id: getIDValue.primaryAuthor?.(r), - label: getDisplayValue.primaryAuthor?.(r), - }))} - isLoading={primaryAuthorLoading} + placeholder=\\"Search Post\\" + value={currentPostsDisplayValue} + options={PostsRecords.map((r) => ({ + id: getIDValue.Posts?.(r), + label: getDisplayValue.Posts?.(r), + }))} + isLoading={PostsLoading} onSelect={({ id, label }) => { - setCurrentPrimaryAuthorValue( - primaryAuthorRecords.find((r) => + setCurrentPostsValue( + PostsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentPrimaryAuthorDisplayValue(label); - runValidationTasks(\\"primaryAuthor\\", label); + setCurrentPostsDisplayValue(label); + runValidationTasks(\\"Posts\\", label); }} onClear={() => { - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setCurrentPostsDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchPostsRecords(value); + if (errors.Posts?.hasError) { + runValidationTasks(\\"Posts\\", value); + } + setCurrentPostsDisplayValue(value); + setCurrentPostsValue(undefined); }} + onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} + errorMessage={errors.Posts?.errorMessage} + hasError={errors.Posts?.hasError} + ref={PostsRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"Posts\\")} + > + + { + let values = items; + if (onChange) { + const modelFields = { + label, + Posts, + statuses: values, + }; + const result = onChange(modelFields); + values = result?.statuses ?? values; + } + setStatuses(values); + setCurrentStatusesValue(\\"\\"); + }} + currentFieldValue={currentStatusesValue} + label={\\"Statuses\\"} + items={statuses} + hasError={errors?.statuses?.hasError} + runValidationTasks={async () => + await runValidationTasks(\\"statuses\\", currentStatusesValue) + } + errorMessage={errors?.statuses?.errorMessage} + getBadgeText={getDisplayValue.statuses} + setFieldValue={setCurrentStatusesValue} + inputFieldRef={statusesRef} + defaultFieldValue={\\"\\"} + > + { let { value } = e.target; - fetchPrimaryAuthorRecords(value); - if (errors.primaryAuthor?.hasError) { - runValidationTasks(\\"primaryAuthor\\", value); + if (errors.statuses?.hasError) { + runValidationTasks(\\"statuses\\", value); } - setCurrentPrimaryAuthorDisplayValue(value); - setCurrentPrimaryAuthorValue(undefined); + setCurrentStatusesValue(value); }} - onBlur={() => - runValidationTasks( - \\"primaryAuthor\\", - currentPrimaryAuthorDisplayValue - ) - } - errorMessage={errors.primaryAuthor?.errorMessage} - hasError={errors.primaryAuthor?.hasError} - ref={primaryAuthorRef} + onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} + errorMessage={errors.statuses?.errorMessage} + hasError={errors.statuses?.hasError} + ref={statusesRef} labelHidden={true} - {...getOverrideProps(overrides, \\"primaryAuthor\\")} - > + {...getOverrideProps(overrides, \\"statuses\\")} + > + + + + + + + + + + + ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Author } from \\"../API\\"; +import { Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type BookCreateFormInputValues = { - name?: string; - primaryAuthor?: Author; +export declare type TagCreateFormInputValues = { + label?: string; + Posts?: Post[]; + statuses?: string[]; }; -export declare type BookCreateFormValidationValues = { - name?: ValidationFunction; - primaryAuthor?: ValidationFunction; +export declare type TagCreateFormValidationValues = { + label?: ValidationFunction; + Posts?: ValidationFunction; + statuses?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type BookCreateFormOverridesProps = { - BookCreateFormGrid?: PrimitiveOverrideProps; - name?: PrimitiveOverrideProps; - primaryAuthor?: PrimitiveOverrideProps; +export declare type TagCreateFormOverridesProps = { + TagCreateFormGrid?: PrimitiveOverrideProps; + label?: PrimitiveOverrideProps; + Posts?: PrimitiveOverrideProps; + statuses?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type BookCreateFormProps = React.PropsWithChildren<{ - overrides?: BookCreateFormOverridesProps | undefined | null; +export declare type TagCreateFormProps = React.PropsWithChildren<{ + overrides?: TagCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onSuccess?: (fields: BookCreateFormInputValues) => void; - onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; + onSuccess?: (fields: TagCreateFormInputValues) => void; + onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onValidate?: BookCreateFormValidationValues; + onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; + onValidate?: TagCreateFormValidationValues; } & React.CSSProperties>; -export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; +export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with id field instead of belongsTo 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -3646,8 +6004,8 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listPosts } from \\"../graphql/queries\\"; -import { createComment } from \\"../graphql/mutations\\"; +import { listAuthors, listTitles } from \\"../graphql/queries\\"; +import { createBook } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -3803,45 +6161,80 @@ function ArrayField({ ); } -export default function CommentCreateForm(props) { +export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, + onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - content: \\"\\", - postID: undefined, + name: \\"\\", + primaryAuthor: undefined, + primaryTitle: undefined, }; - const [content, setContent] = React.useState(initialValues.content); - const [postID, setPostID] = React.useState(initialValues.postID); - const [postIDLoading, setPostIDLoading] = React.useState(false); - const [postIDRecords, setPostIDRecords] = React.useState([]); - const [selectedPostIDRecords, setSelectedPostIDRecords] = React.useState([]); + const [name, setName] = React.useState(initialValues.name); + const [primaryAuthor, setPrimaryAuthor] = React.useState( + initialValues.primaryAuthor + ); + const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); + const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); + const [primaryTitle, setPrimaryTitle] = React.useState( + initialValues.primaryTitle + ); + const [primaryTitleLoading, setPrimaryTitleLoading] = React.useState(false); + const [primaryTitleRecords, setPrimaryTitleRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setContent(initialValues.content); - setPostID(initialValues.postID); - setCurrentPostIDValue(undefined); - setCurrentPostIDDisplayValue(\\"\\"); + setName(initialValues.name); + setPrimaryAuthor(initialValues.primaryAuthor); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setPrimaryTitle(initialValues.primaryTitle); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(\\"\\"); setErrors({}); }; - const [currentPostIDDisplayValue, setCurrentPostIDDisplayValue] = + const [ + currentPrimaryAuthorDisplayValue, + setCurrentPrimaryAuthorDisplayValue, + ] = React.useState(\\"\\"); + const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = + React.useState(undefined); + const primaryAuthorRef = React.createRef(); + const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = React.useState(\\"\\"); - const [currentPostIDValue, setCurrentPostIDValue] = React.useState(undefined); - const postIDRef = React.createRef(); + const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = + React.useState(undefined); + const primaryTitleRef = React.createRef(); + const getIDValue = { + primaryAuthor: (r) => JSON.stringify({ id: r?.id }), + primaryTitle: (r) => JSON.stringify({ id: r?.id }), + }; + const primaryAuthorIdSet = new Set( + Array.isArray(primaryAuthor) + ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) + : getIDValue.primaryAuthor?.(primaryAuthor) + ); + const primaryTitleIdSet = new Set( + Array.isArray(primaryTitle) + ? primaryTitle.map((r) => getIDValue.primaryTitle?.(r)) + : getIDValue.primaryTitle?.(primaryTitle) + ); const getDisplayValue = { - postID: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, + primaryAuthor: (r) => r?.name, + primaryTitle: (r) => r?.name, }; const validations = { - content: [{ type: \\"Required\\" }], - postID: [], + name: [], + primaryAuthor: [], + primaryTitle: [], }; const runValidationTasks = async ( fieldName, @@ -3860,35 +6253,63 @@ export default function CommentCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchPostIDRecords = async (value) => { - setPostIDLoading(true); + const fetchPrimaryAuthorRecords = async (value) => { + setPrimaryAuthorLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { - or: [{ title: { contains: value } }, { id: { contains: value } }], - }, + filter: { or: [{ name: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listPosts.replaceAll(\\"__typename\\", \\"\\"), + query: listAuthors.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listPosts?.items; - var loaded = result.filter((item) => postID !== item.id); + )?.data?.listAuthors?.items; + var loaded = result.filter( + (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) + ); newOptions.push(...loaded); newNext = result.nextToken; } - setPostIDRecords(newOptions.slice(0, autocompleteLength)); - setPostIDLoading(false); + setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); + setPrimaryAuthorLoading(false); + }; + const fetchPrimaryTitleRecords = async (value) => { + setPrimaryTitleLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ name: { contains: value } }] }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTitles.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listTitles?.items; + var loaded = result.filter( + (item) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setPrimaryTitleRecords(newOptions.slice(0, autocompleteLength)); + setPrimaryTitleLoading(false); }; React.useEffect(() => { - fetchPostIDRecords(\\"\\"); + fetchPrimaryAuthorRecords(\\"\\"); + fetchPrimaryTitleRecords(\\"\\"); }, []); return ( { event.preventDefault(); let modelFields = { - content, - postID, + name, + primaryAuthor, + primaryTitle, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks(fieldName, item) + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) ) ); return promises; } promises.push( - runValidationTasks(fieldName, modelFields[fieldName]) + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) ); return promises; }, []) @@ -3930,11 +6360,16 @@ export default function CommentCreateForm(props) { modelFields[key] = null; } }); + const modelFieldsToSave = { + name: modelFields.name, + authorId: modelFields?.primaryAuthor?.id, + titleId: modelFields?.primaryTitle?.id, + }; await API.graphql({ - query: createComment.replaceAll(\\"__typename\\", \\"\\"), + query: createBook.replaceAll(\\"__typename\\", \\"\\"), variables: { input: { - ...modelFields, + ...modelFieldsToSave, }, }, }); @@ -3951,33 +6386,68 @@ export default function CommentCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"CommentCreateForm\\")} + {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > + + + + + + + { let { value } = e.target; if (onChange) { const modelFields = { - content: value, - postID, + name: value, + primaryAuthor, + primaryTitle, }; const result = onChange(modelFields); - value = result?.content ?? value; + value = result?.name ?? value; } - if (errors.content?.hasError) { - runValidationTasks(\\"content\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setContent(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"content\\", content)} - errorMessage={errors.content?.errorMessage} - hasError={errors.content?.hasError} - {...getOverrideProps(overrides, \\"content\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} > - await runValidationTasks(\\"postID\\", currentPostIDValue) - } - errorMessage={errors?.postID?.errorMessage} - getBadgeText={(value) => - value - ? getDisplayValue.postID( - postIDRecords.find((r) => r.id === value) ?? - selectedPostIDRecords.find((r) => r.id === value) - ) - : \\"\\" + await runValidationTasks(\\"primaryAuthor\\", currentPrimaryAuthorValue) } - setFieldValue={(value) => { - setCurrentPostIDDisplayValue( - value - ? getDisplayValue.postID( - postIDRecords.find((r) => r.id === value) ?? - selectedPostIDRecords.find((r) => r.id === value) - ) - : \\"\\" + errorMessage={errors?.primaryAuthor?.errorMessage} + getBadgeText={getDisplayValue.primaryAuthor} + setFieldValue={(model) => { + setCurrentPrimaryAuthorDisplayValue( + model ? getDisplayValue.primaryAuthor(model) : \\"\\" ); - setCurrentPostIDValue(value); - const selectedRecord = postIDRecords.find((r) => r.id === value); - if (selectedRecord) { - setSelectedPostIDRecords([selectedRecord]); - } + setCurrentPrimaryAuthorValue(model); }} - inputFieldRef={postIDRef} + inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > - arr.findIndex((member) => member?.id === r?.id) === i + (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) ) .map((r) => ({ - id: r?.id, - label: getDisplayValue.postID?.(r), + id: getIDValue.primaryAuthor?.(r), + label: getDisplayValue.primaryAuthor?.(r), }))} - isLoading={postIDLoading} + isLoading={primaryAuthorLoading} onSelect={({ id, label }) => { - setCurrentPostIDValue(id); - setCurrentPostIDDisplayValue(label); - runValidationTasks(\\"postID\\", label); + setCurrentPrimaryAuthorValue( + primaryAuthorRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentPrimaryAuthorDisplayValue(label); + runValidationTasks(\\"primaryAuthor\\", label); + }} + onClear={() => { + setCurrentPrimaryAuthorDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchPrimaryAuthorRecords(value); + if (errors.primaryAuthor?.hasError) { + runValidationTasks(\\"primaryAuthor\\", value); + } + setCurrentPrimaryAuthorDisplayValue(value); + setCurrentPrimaryAuthorValue(undefined); + }} + onBlur={() => + runValidationTasks( + \\"primaryAuthor\\", + currentPrimaryAuthorDisplayValue + ) + } + errorMessage={errors.primaryAuthor?.errorMessage} + hasError={errors.primaryAuthor?.hasError} + ref={primaryAuthorRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"primaryAuthor\\")} + > + + { + let value = items[0]; + if (onChange) { + const modelFields = { + name, + primaryAuthor, + primaryTitle: value, + }; + const result = onChange(modelFields); + value = result?.primaryTitle ?? value; + } + setPrimaryTitle(value); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(\\"\\"); + }} + currentFieldValue={currentPrimaryTitleValue} + label={\\"Primary title\\"} + items={primaryTitle ? [primaryTitle] : []} + hasError={errors?.primaryTitle?.hasError} + runValidationTasks={async () => + await runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleValue) + } + errorMessage={errors?.primaryTitle?.errorMessage} + getBadgeText={getDisplayValue.primaryTitle} + setFieldValue={(model) => { + setCurrentPrimaryTitleDisplayValue( + model ? getDisplayValue.primaryTitle(model) : \\"\\" + ); + setCurrentPrimaryTitleValue(model); + }} + inputFieldRef={primaryTitleRef} + defaultFieldValue={\\"\\"} + > + !primaryTitleIdSet.has(getIDValue.primaryTitle?.(r))) + .map((r) => ({ + id: getIDValue.primaryTitle?.(r), + label: getDisplayValue.primaryTitle?.(r), + }))} + isLoading={primaryTitleLoading} + onSelect={({ id, label }) => { + setCurrentPrimaryTitleValue( + primaryTitleRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentPrimaryTitleDisplayValue(label); + runValidationTasks(\\"primaryTitle\\", label); }} onClear={() => { - setCurrentPostIDDisplayValue(\\"\\"); + setCurrentPrimaryTitleDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchPostIDRecords(value); - if (errors.postID?.hasError) { - runValidationTasks(\\"postID\\", value); + fetchPrimaryTitleRecords(value); + if (errors.primaryTitle?.hasError) { + runValidationTasks(\\"primaryTitle\\", value); } - setCurrentPostIDDisplayValue(value); - setCurrentPostIDValue(undefined); + setCurrentPrimaryTitleDisplayValue(value); + setCurrentPrimaryTitleValue(undefined); }} - onBlur={() => runValidationTasks(\\"postID\\", currentPostIDValue)} - errorMessage={errors.postID?.errorMessage} - hasError={errors.postID?.hasError} - ref={postIDRef} + onBlur={() => + runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleDisplayValue) + } + errorMessage={errors.primaryTitle?.errorMessage} + hasError={errors.primaryTitle?.hasError} + ref={primaryTitleRef} labelHidden={true} - {...getOverrideProps(overrides, \\"postID\\")} + {...getOverrideProps(overrides, \\"primaryTitle\\")} > - - - - - - ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with id field instead of belongsTo 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Author, Title } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type CommentCreateFormInputValues = { - content?: string; - postID?: string; +export declare type BookCreateFormInputValues = { + name?: string; + primaryAuthor?: Author; + primaryTitle?: Title; }; -export declare type CommentCreateFormValidationValues = { - content?: ValidationFunction; - postID?: ValidationFunction; +export declare type BookCreateFormValidationValues = { + name?: ValidationFunction; + primaryAuthor?: ValidationFunction; + primaryTitle?: ValidationFunction; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type CommentCreateFormOverridesProps = { - CommentCreateFormGrid?: PrimitiveOverrideProps<GridProps>; - content?: PrimitiveOverrideProps<TextFieldProps>; - postID?: PrimitiveOverrideProps<AutocompleteProps>; +export declare type BookCreateFormOverridesProps = { + BookCreateFormGrid?: PrimitiveOverrideProps<GridProps>; + name?: PrimitiveOverrideProps<TextFieldProps>; + primaryAuthor?: PrimitiveOverrideProps<AutocompleteProps>; + primaryTitle?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type CommentCreateFormProps = React.PropsWithChildren<{ - overrides?: CommentCreateFormOverridesProps | undefined | null; +export declare type BookCreateFormProps = React.PropsWithChildren<{ + overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: CommentCreateFormInputValues) => CommentCreateFormInputValues; - onSuccess?: (fields: CommentCreateFormInputValues) => void; - onError?: (fields: CommentCreateFormInputValues, errorMessage: string) => void; - onChange?: (fields: CommentCreateFormInputValues) => CommentCreateFormInputValues; - onValidate?: CommentCreateFormValidationValues; + onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onSuccess?: (fields: BookCreateFormInputValues) => void; + onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; -export default function CommentCreateForm(props: CommentCreateFormProps): React.ReactElement; +export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple relationship & cpk 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -4150,17 +6676,24 @@ import { Grid, Icon, ScrollView, - SelectField, Text, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Post } from \\"../API\\"; +import { CPKClass } from \\"../API\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listPosts } from \\"../graphql/queries\\"; -import { createTag, createTagPost } from \\"../graphql/mutations\\"; +import { + listCPKClasses, + listCPKProjects, + listCPKStudents, +} from \\"../graphql/queries\\"; +import { + createCPKTeacher, + createCPKTeacherCPKClass, + updateCPKProject, +} from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -4316,115 +6849,202 @@ function ArrayField({ </React.Fragment> ); } -export default function TagCreateForm(props) { +export default function CreateCPKTeacherForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - label: \\"\\", - Posts: [], - statuses: [], + specialTeacherId: \\"\\", + CPKStudent: undefined, + CPKClasses: [], + CPKProjects: [], }; - const [label, setLabel] = React.useState(initialValues.label); - const [Posts, setPosts] = React.useState(initialValues.Posts); - const [PostsLoading, setPostsLoading] = React.useState(false); - const [PostsRecords, setPostsRecords] = React.useState([]); - const [statuses, setStatuses] = React.useState(initialValues.statuses); + const [specialTeacherId, setSpecialTeacherId] = React.useState( + initialValues.specialTeacherId + ); + const [CPKStudent, setCPKStudent] = React.useState(initialValues.CPKStudent); + const [CPKStudentLoading, setCPKStudentLoading] = React.useState(false); + const [CPKStudentRecords, setCPKStudentRecords] = React.useState([]); + const [CPKClasses, setCPKClasses] = React.useState(initialValues.CPKClasses); + const [CPKClassesLoading, setCPKClassesLoading] = React.useState(false); + const [CPKClassesRecords, setCPKClassesRecords] = React.useState([]); + const [CPKProjects, setCPKProjects] = React.useState( + initialValues.CPKProjects + ); + const [CPKProjectsLoading, setCPKProjectsLoading] = React.useState(false); + const [CPKProjectsRecords, setCPKProjectsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setLabel(initialValues.label); - setPosts(initialValues.Posts); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); - setStatuses(initialValues.statuses); - setCurrentStatusesValue(\\"\\"); + setSpecialTeacherId(initialValues.specialTeacherId); + setCPKStudent(initialValues.CPKStudent); + setCurrentCPKStudentValue(undefined); + setCurrentCPKStudentDisplayValue(\\"\\"); + setCPKClasses(initialValues.CPKClasses); + setCurrentCPKClassesValue(undefined); + setCurrentCPKClassesDisplayValue(\\"\\"); + setCPKProjects(initialValues.CPKProjects); + setCurrentCPKProjectsValue(undefined); + setCurrentCPKProjectsDisplayValue(\\"\\"); setErrors({}); }; - const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = + const [currentCPKStudentDisplayValue, setCurrentCPKStudentDisplayValue] = React.useState(\\"\\"); - const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); - const PostsRef = React.createRef(); - const [currentStatusesValue, setCurrentStatusesValue] = React.useState(\\"\\"); - const statusesRef = React.createRef(); + const [currentCPKStudentValue, setCurrentCPKStudentValue] = + React.useState(undefined); + const CPKStudentRef = React.createRef(); + const [currentCPKClassesDisplayValue, setCurrentCPKClassesDisplayValue] = + React.useState(\\"\\"); + const [currentCPKClassesValue, setCurrentCPKClassesValue] = + React.useState(undefined); + const CPKClassesRef = React.createRef(); + const [currentCPKProjectsDisplayValue, setCurrentCPKProjectsDisplayValue] = + React.useState(\\"\\"); + const [currentCPKProjectsValue, setCurrentCPKProjectsValue] = + React.useState(undefined); + const CPKProjectsRef = React.createRef(); const getIDValue = { - Posts: (r) => JSON.stringify({ id: r?.id }), + CPKStudent: (r) => + JSON.stringify({ specialStudentId: r?.specialStudentId }), + CPKClasses: (r) => JSON.stringify({ specialClassId: r?.specialClassId }), + CPKProjects: (r) => + JSON.stringify({ specialProjectId: r?.specialProjectId }), }; - const PostsIdSet = new Set( - Array.isArray(Posts) - ? Posts.map((r) => getIDValue.Posts?.(r)) - : getIDValue.Posts?.(Posts) + const CPKStudentIdSet = new Set( + Array.isArray(CPKStudent) + ? CPKStudent.map((r) => getIDValue.CPKStudent?.(r)) + : getIDValue.CPKStudent?.(CPKStudent) + ); + const CPKClassesIdSet = new Set( + Array.isArray(CPKClasses) + ? CPKClasses.map((r) => getIDValue.CPKClasses?.(r)) + : getIDValue.CPKClasses?.(CPKClasses) + ); + const CPKProjectsIdSet = new Set( + Array.isArray(CPKProjects) + ? CPKProjects.map((r) => getIDValue.CPKProjects?.(r)) + : getIDValue.CPKProjects?.(CPKProjects) ); const getDisplayValue = { - Posts: (r) => r?.title, - statuses: (r) => { - const enumDisplayValueMap = { - PENDING: \\"Pending\\", - POSTED: \\"Posted\\", - IN_REVIEW: \\"In review\\", - }; - return enumDisplayValueMap[r]; - }, + CPKStudent: (r) => r?.specialStudentId, + CPKClasses: (r) => r?.specialClassId, + CPKProjects: (r) => r?.specialProjectId, }; const validations = { - label: [], - Posts: [], - statuses: [], + specialTeacherId: [{ type: \\"Required\\" }], + CPKStudent: [], + CPKClasses: [], + CPKProjects: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + const fetchCPKStudentRecords = async (value) => { + setCPKStudentLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ specialStudentId: { contains: value } }] }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listCPKStudents.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listCPKStudents?.items; + var loaded = result.filter( + (item) => !CPKStudentIdSet.has(getIDValue.CPKStudent?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setCPKStudentRecords(newOptions.slice(0, autocompleteLength)); + setCPKStudentLoading(false); }; - const runValidationTasks = async ( - fieldName, - currentValue, - getDisplayValue - ) => { - const value = - currentValue && getDisplayValue - ? getDisplayValue(currentValue) - : currentValue; - let validationResponse = validateField(value, validations[fieldName]); - const customValidator = fetchByPath(onValidate, fieldName); - if (customValidator) { - validationResponse = await customValidator(value, validationResponse); + const fetchCPKClassesRecords = async (value) => { + setCPKClassesLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ specialClassId: { contains: value } }] }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listCPKClasses.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listCPKClasses?.items; + var loaded = result.filter( + (item) => !CPKClassesIdSet.has(getIDValue.CPKClasses?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; } - setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); - return validationResponse; + setCPKClassesRecords(newOptions.slice(0, autocompleteLength)); + setCPKClassesLoading(false); }; - const fetchPostsRecords = async (value) => { - setPostsLoading(true); + const fetchCPKProjectsRecords = async (value) => { + setCPKProjectsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { or: [{ title: { contains: value } }] }, + filter: { or: [{ specialProjectId: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listPosts.replaceAll(\\"__typename\\", \\"\\"), + query: listCPKProjects.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listPosts?.items; + )?.data?.listCPKProjects?.items; var loaded = result.filter( - (item) => !PostsIdSet.has(getIDValue.Posts?.(item)) + (item) => !CPKProjectsIdSet.has(getIDValue.CPKProjects?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setPostsRecords(newOptions.slice(0, autocompleteLength)); - setPostsLoading(false); + setCPKProjectsRecords(newOptions.slice(0, autocompleteLength)); + setCPKProjectsLoading(false); }; React.useEffect(() => { - fetchPostsRecords(\\"\\"); + fetchCPKStudentRecords(\\"\\"); + fetchCPKClassesRecords(\\"\\"); + fetchCPKProjectsRecords(\\"\\"); }, []); return ( <Grid @@ -4435,9 +7055,10 @@ export default function TagCreateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - label, - Posts, - statuses, + specialTeacherId, + CPKStudent, + CPKClasses, + CPKProjects, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -4476,29 +7097,46 @@ export default function TagCreateForm(props) { } }); const modelFieldsToSave = { - label: modelFields.label, - statuses: modelFields.statuses, + specialTeacherId: modelFields.specialTeacherId, + cPKTeacherCPKStudentSpecialStudentId: + modelFields?.CPKStudent?.specialStudentId, }; - const tag = ( + const cPKTeacher = ( await API.graphql({ - query: createTag.replaceAll(\\"__typename\\", \\"\\"), + query: createCPKTeacher.replaceAll(\\"__typename\\", \\"\\"), variables: { input: { ...modelFieldsToSave, }, }, }) - )?.data?.createTag; + )?.data?.createCPKTeacher; const promises = []; promises.push( - ...Posts.reduce((promises, post) => { + ...CPKClasses.reduce((promises, cpkClass) => { promises.push( API.graphql({ - query: createTagPost.replaceAll(\\"__typename\\", \\"\\"), + query: createCPKTeacherCPKClass.replaceAll(\\"__typename\\", \\"\\"), variables: { input: { - tagID: tag.id, - postID: Post.id, + cPKTeacherSpecialTeacherId: cPKTeacher.specialTeacherId, + cPKClassSpecialClassId: CPKClass.specialClassId, + }, + }, + }) + ); + return promises; + }, []) + ); + promises.push( + ...CPKProjects.reduce((promises, original) => { + promises.push( + API.graphql({ + query: updateCPKProject.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + specialProjectId: original.specialProjectId, + cPKTeacherID: cPKTeacher.specialTeacherId, }, }, }) @@ -4520,109 +7158,195 @@ export default function TagCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"TagCreateForm\\")} + {...getOverrideProps(overrides, \\"CreateCPKTeacherForm\\")} {...rest} > <TextField - label=\\"Label\\" - isRequired={false} + label=\\"Special teacher id\\" + isRequired={true} isReadOnly={false} - value={label} + value={specialTeacherId} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - label: value, - Posts, - statuses, + specialTeacherId: value, + CPKStudent, + CPKClasses, + CPKProjects, }; const result = onChange(modelFields); - value = result?.label ?? value; + value = result?.specialTeacherId ?? value; } - if (errors.label?.hasError) { - runValidationTasks(\\"label\\", value); + if (errors.specialTeacherId?.hasError) { + runValidationTasks(\\"specialTeacherId\\", value); } - setLabel(value); + setSpecialTeacherId(value); }} - onBlur={() => runValidationTasks(\\"label\\", label)} - errorMessage={errors.label?.errorMessage} - hasError={errors.label?.hasError} - {...getOverrideProps(overrides, \\"label\\")} + onBlur={() => runValidationTasks(\\"specialTeacherId\\", specialTeacherId)} + errorMessage={errors.specialTeacherId?.errorMessage} + hasError={errors.specialTeacherId?.hasError} + {...getOverrideProps(overrides, \\"specialTeacherId\\")} ></TextField> + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; + if (onChange) { + const modelFields = { + specialTeacherId, + CPKStudent: value, + CPKClasses, + CPKProjects, + }; + const result = onChange(modelFields); + value = result?.CPKStudent ?? value; + } + setCPKStudent(value); + setCurrentCPKStudentValue(undefined); + setCurrentCPKStudentDisplayValue(\\"\\"); + }} + currentFieldValue={currentCPKStudentValue} + label={\\"Cpk student\\"} + items={CPKStudent ? [CPKStudent] : []} + hasError={errors?.CPKStudent?.hasError} + runValidationTasks={async () => + await runValidationTasks(\\"CPKStudent\\", currentCPKStudentValue) + } + errorMessage={errors?.CPKStudent?.errorMessage} + getBadgeText={getDisplayValue.CPKStudent} + setFieldValue={(model) => { + setCurrentCPKStudentDisplayValue( + model ? getDisplayValue.CPKStudent(model) : \\"\\" + ); + setCurrentCPKStudentValue(model); + }} + inputFieldRef={CPKStudentRef} + defaultFieldValue={\\"\\"} + > + <Autocomplete + label=\\"Cpk student\\" + isRequired={false} + isReadOnly={false} + placeholder=\\"Search CPKStudent\\" + value={currentCPKStudentDisplayValue} + options={CPKStudentRecords.filter( + (r) => !CPKStudentIdSet.has(getIDValue.CPKStudent?.(r)) + ).map((r) => ({ + id: getIDValue.CPKStudent?.(r), + label: getDisplayValue.CPKStudent?.(r), + }))} + isLoading={CPKStudentLoading} + onSelect={({ id, label }) => { + setCurrentCPKStudentValue( + CPKStudentRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentCPKStudentDisplayValue(label); + runValidationTasks(\\"CPKStudent\\", label); + }} + onClear={() => { + setCurrentCPKStudentDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchCPKStudentRecords(value); + if (errors.CPKStudent?.hasError) { + runValidationTasks(\\"CPKStudent\\", value); + } + setCurrentCPKStudentDisplayValue(value); + setCurrentCPKStudentValue(undefined); + }} + onBlur={() => + runValidationTasks(\\"CPKStudent\\", currentCPKStudentDisplayValue) + } + errorMessage={errors.CPKStudent?.errorMessage} + hasError={errors.CPKStudent?.hasError} + ref={CPKStudentRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"CPKStudent\\")} + ></Autocomplete> + </ArrayField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { - label, - Posts: values, - statuses, + specialTeacherId, + CPKStudent, + CPKClasses: values, + CPKProjects, }; const result = onChange(modelFields); - values = result?.Posts ?? values; + values = result?.CPKClasses ?? values; } - setPosts(values); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); + setCPKClasses(values); + setCurrentCPKClassesValue(undefined); + setCurrentCPKClassesDisplayValue(\\"\\"); }} - currentFieldValue={currentPostsValue} - label={\\"Posts\\"} - items={Posts} - hasError={errors?.Posts?.hasError} + currentFieldValue={currentCPKClassesValue} + label={\\"Cpk classes\\"} + items={CPKClasses} + hasError={errors?.CPKClasses?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"Posts\\", currentPostsValue) + await runValidationTasks(\\"CPKClasses\\", currentCPKClassesValue) } - errorMessage={errors?.Posts?.errorMessage} - getBadgeText={getDisplayValue.Posts} + errorMessage={errors?.CPKClasses?.errorMessage} + getBadgeText={getDisplayValue.CPKClasses} setFieldValue={(model) => { - setCurrentPostsDisplayValue( - model ? getDisplayValue.Posts(model) : \\"\\" + setCurrentCPKClassesDisplayValue( + model ? getDisplayValue.CPKClasses(model) : \\"\\" ); - setCurrentPostsValue(model); + setCurrentCPKClassesValue(model); }} - inputFieldRef={PostsRef} + inputFieldRef={CPKClassesRef} defaultFieldValue={\\"\\"} > <Autocomplete - label=\\"Posts\\" + label=\\"Cpk classes\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search Post\\" - value={currentPostsDisplayValue} - options={PostsRecords.map((r) => ({ - id: getIDValue.Posts?.(r), - label: getDisplayValue.Posts?.(r), + placeholder=\\"Search CPKClass\\" + value={currentCPKClassesDisplayValue} + options={CPKClassesRecords.map((r) => ({ + id: getIDValue.CPKClasses?.(r), + label: getDisplayValue.CPKClasses?.(r), }))} - isLoading={PostsLoading} + isLoading={CPKClassesLoading} onSelect={({ id, label }) => { - setCurrentPostsValue( - PostsRecords.find((r) => + setCurrentCPKClassesValue( + CPKClassesRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentPostsDisplayValue(label); - runValidationTasks(\\"Posts\\", label); + setCurrentCPKClassesDisplayValue(label); + runValidationTasks(\\"CPKClasses\\", label); }} onClear={() => { - setCurrentPostsDisplayValue(\\"\\"); + setCurrentCPKClassesDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchPostsRecords(value); - if (errors.Posts?.hasError) { - runValidationTasks(\\"Posts\\", value); + fetchCPKClassesRecords(value); + if (errors.CPKClasses?.hasError) { + runValidationTasks(\\"CPKClasses\\", value); } - setCurrentPostsDisplayValue(value); - setCurrentPostsValue(undefined); + setCurrentCPKClassesDisplayValue(value); + setCurrentCPKClassesValue(undefined); }} - onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} - errorMessage={errors.Posts?.errorMessage} - hasError={errors.Posts?.hasError} - ref={PostsRef} + onBlur={() => + runValidationTasks(\\"CPKClasses\\", currentCPKClassesDisplayValue) + } + errorMessage={errors.CPKClasses?.errorMessage} + hasError={errors.CPKClasses?.hasError} + ref={CPKClassesRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Posts\\")} + {...getOverrideProps(overrides, \\"CPKClasses\\")} ></Autocomplete> </ArrayField> <ArrayField @@ -4630,64 +7354,81 @@ export default function TagCreateForm(props) { let values = items; if (onChange) { const modelFields = { - label, - Posts, - statuses: values, + specialTeacherId, + CPKStudent, + CPKClasses, + CPKProjects: values, }; const result = onChange(modelFields); - values = result?.statuses ?? values; + values = result?.CPKProjects ?? values; } - setStatuses(values); - setCurrentStatusesValue(\\"\\"); + setCPKProjects(values); + setCurrentCPKProjectsValue(undefined); + setCurrentCPKProjectsDisplayValue(\\"\\"); }} - currentFieldValue={currentStatusesValue} - label={\\"Statuses\\"} - items={statuses} - hasError={errors?.statuses?.hasError} + currentFieldValue={currentCPKProjectsValue} + label={\\"Cpk projects\\"} + items={CPKProjects} + hasError={errors?.CPKProjects?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"statuses\\", currentStatusesValue) + await runValidationTasks(\\"CPKProjects\\", currentCPKProjectsValue) } - errorMessage={errors?.statuses?.errorMessage} - getBadgeText={getDisplayValue.statuses} - setFieldValue={setCurrentStatusesValue} - inputFieldRef={statusesRef} + errorMessage={errors?.CPKProjects?.errorMessage} + getBadgeText={getDisplayValue.CPKProjects} + setFieldValue={(model) => { + setCurrentCPKProjectsDisplayValue( + model ? getDisplayValue.CPKProjects(model) : \\"\\" + ); + setCurrentCPKProjectsValue(model); + }} + inputFieldRef={CPKProjectsRef} defaultFieldValue={\\"\\"} > - <SelectField - label=\\"Statuses\\" - placeholder=\\"Please select an option\\" - isDisabled={false} - value={currentStatusesValue} + <Autocomplete + label=\\"Cpk projects\\" + isRequired={false} + isReadOnly={false} + placeholder=\\"Search CPKProject\\" + value={currentCPKProjectsDisplayValue} + options={CPKProjectsRecords.filter( + (r) => !CPKProjectsIdSet.has(getIDValue.CPKProjects?.(r)) + ).map((r) => ({ + id: getIDValue.CPKProjects?.(r), + label: getDisplayValue.CPKProjects?.(r), + }))} + isLoading={CPKProjectsLoading} + onSelect={({ id, label }) => { + setCurrentCPKProjectsValue( + CPKProjectsRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentCPKProjectsDisplayValue(label); + runValidationTasks(\\"CPKProjects\\", label); + }} + onClear={() => { + setCurrentCPKProjectsDisplayValue(\\"\\"); + }} onChange={(e) => { let { value } = e.target; - if (errors.statuses?.hasError) { - runValidationTasks(\\"statuses\\", value); + fetchCPKProjectsRecords(value); + if (errors.CPKProjects?.hasError) { + runValidationTasks(\\"CPKProjects\\", value); } - setCurrentStatusesValue(value); + setCurrentCPKProjectsDisplayValue(value); + setCurrentCPKProjectsValue(undefined); }} - onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} - errorMessage={errors.statuses?.errorMessage} - hasError={errors.statuses?.hasError} - ref={statusesRef} + onBlur={() => + runValidationTasks(\\"CPKProjects\\", currentCPKProjectsDisplayValue) + } + errorMessage={errors.CPKProjects?.errorMessage} + hasError={errors.CPKProjects?.hasError} + ref={CPKProjectsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"statuses\\")} - > - <option - children=\\"Pending\\" - value=\\"PENDING\\" - {...getOverrideProps(overrides, \\"statusesoption0\\")} - ></option> - <option - children=\\"Posted\\" - value=\\"POSTED\\" - {...getOverrideProps(overrides, \\"statusesoption1\\")} - ></option> - <option - children=\\"In review\\" - value=\\"IN_REVIEW\\" - {...getOverrideProps(overrides, \\"statusesoption2\\")} - ></option> - </SelectField> + {...getOverrideProps(overrides, \\"CPKProjects\\")} + ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" @@ -4706,14 +7447,6 @@ export default function TagCreateForm(props) { gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> <Button children=\\"Submit\\" type=\\"submit\\" @@ -4729,49 +7462,51 @@ export default function TagCreateForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple relationship & cpk 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Post } from \\"../API\\"; +import { CPKClass, CPKProject, CPKStudent } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type TagCreateFormInputValues = { - label?: string; - Posts?: Post[]; - statuses?: string[]; +export declare type CreateCPKTeacherFormInputValues = { + specialTeacherId?: string; + CPKStudent?: CPKStudent; + CPKClasses?: CPKClass[]; + CPKProjects?: CPKProject[]; }; -export declare type TagCreateFormValidationValues = { - label?: ValidationFunction<string>; - Posts?: ValidationFunction<Post>; - statuses?: ValidationFunction<string>; +export declare type CreateCPKTeacherFormValidationValues = { + specialTeacherId?: ValidationFunction<string>; + CPKStudent?: ValidationFunction<CPKStudent>; + CPKClasses?: ValidationFunction<CPKClass>; + CPKProjects?: ValidationFunction<CPKProject>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type TagCreateFormOverridesProps = { - TagCreateFormGrid?: PrimitiveOverrideProps<GridProps>; - label?: PrimitiveOverrideProps<TextFieldProps>; - Posts?: PrimitiveOverrideProps<AutocompleteProps>; - statuses?: PrimitiveOverrideProps<SelectFieldProps>; +export declare type CreateCPKTeacherFormOverridesProps = { + CreateCPKTeacherFormGrid?: PrimitiveOverrideProps<GridProps>; + specialTeacherId?: PrimitiveOverrideProps<TextFieldProps>; + CPKStudent?: PrimitiveOverrideProps<AutocompleteProps>; + CPKClasses?: PrimitiveOverrideProps<AutocompleteProps>; + CPKProjects?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type TagCreateFormProps = React.PropsWithChildren<{ - overrides?: TagCreateFormOverridesProps | undefined | null; +export declare type CreateCPKTeacherFormProps = React.PropsWithChildren<{ + overrides?: CreateCPKTeacherFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; - onSuccess?: (fields: TagCreateFormInputValues) => void; - onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; - onValidate?: TagCreateFormValidationValues; + onSubmit?: (fields: CreateCPKTeacherFormInputValues) => CreateCPKTeacherFormInputValues; + onSuccess?: (fields: CreateCPKTeacherFormInputValues) => void; + onError?: (fields: CreateCPKTeacherFormInputValues, errorMessage: string) => void; + onChange?: (fields: CreateCPKTeacherFormInputValues) => CreateCPKTeacherFormInputValues; + onValidate?: CreateCPKTeacherFormValidationValues; } & React.CSSProperties>; -export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; +export default function CreateCPKTeacherForm(props: CreateCPKTeacherFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with nonModel field 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -4784,14 +7519,15 @@ import { Icon, ScrollView, Text, + TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listAuthors, listTitles } from \\"../graphql/queries\\"; -import { createBook } from \\"../graphql/mutations\\"; +import { listParentTables } from \\"../graphql/queries\\"; +import { createBasicTable } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -4947,13 +7683,12 @@ function ArrayField({ </React.Fragment> ); } -export default function BookCreateForm(props) { +export default function CreateForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, @@ -4961,66 +7696,46 @@ export default function BookCreateForm(props) { } = props; const initialValues = { name: \\"\\", - primaryAuthor: undefined, - primaryTitle: undefined, + nmTest: \\"\\", + parentTable: undefined, }; const [name, setName] = React.useState(initialValues.name); - const [primaryAuthor, setPrimaryAuthor] = React.useState( - initialValues.primaryAuthor - ); - const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); - const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); - const [primaryTitle, setPrimaryTitle] = React.useState( - initialValues.primaryTitle + const [nmTest, setNmTest] = React.useState(initialValues.nmTest); + const [parentTable, setParentTable] = React.useState( + initialValues.parentTable ); - const [primaryTitleLoading, setPrimaryTitleLoading] = React.useState(false); - const [primaryTitleRecords, setPrimaryTitleRecords] = React.useState([]); - const autocompleteLength = 10; - const [errors, setErrors] = React.useState({}); - const resetStateValues = () => { - setName(initialValues.name); - setPrimaryAuthor(initialValues.primaryAuthor); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); - setPrimaryTitle(initialValues.primaryTitle); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(\\"\\"); + const [parentTableLoading, setParentTableLoading] = React.useState(false); + const [parentTableRecords, setParentTableRecords] = React.useState([]); + const autocompleteLength = 10; + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setName(initialValues.name); + setNmTest(initialValues.nmTest); + setParentTable(initialValues.parentTable); + setCurrentParentTableValue(undefined); + setCurrentParentTableDisplayValue(\\"\\"); setErrors({}); }; - const [ - currentPrimaryAuthorDisplayValue, - setCurrentPrimaryAuthorDisplayValue, - ] = React.useState(\\"\\"); - const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = - React.useState(undefined); - const primaryAuthorRef = React.createRef(); - const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = + const [currentParentTableDisplayValue, setCurrentParentTableDisplayValue] = React.useState(\\"\\"); - const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = + const [currentParentTableValue, setCurrentParentTableValue] = React.useState(undefined); - const primaryTitleRef = React.createRef(); + const parentTableRef = React.createRef(); const getIDValue = { - primaryAuthor: (r) => JSON.stringify({ id: r?.id }), - primaryTitle: (r) => JSON.stringify({ id: r?.id }), + parentTable: (r) => JSON.stringify({ id: r?.id }), }; - const primaryAuthorIdSet = new Set( - Array.isArray(primaryAuthor) - ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) - : getIDValue.primaryAuthor?.(primaryAuthor) - ); - const primaryTitleIdSet = new Set( - Array.isArray(primaryTitle) - ? primaryTitle.map((r) => getIDValue.primaryTitle?.(r)) - : getIDValue.primaryTitle?.(primaryTitle) + const parentTableIdSet = new Set( + Array.isArray(parentTable) + ? parentTable.map((r) => getIDValue.parentTable?.(r)) + : getIDValue.parentTable?.(parentTable) ); const getDisplayValue = { - primaryAuthor: (r) => r?.name, - primaryTitle: (r) => r?.name, + parentTable: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], - primaryAuthor: [], - primaryTitle: [], + nmTest: [{ type: \\"JSON\\" }], + parentTable: [], }; const runValidationTasks = async ( fieldName, @@ -5039,63 +7754,37 @@ export default function BookCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchPrimaryAuthorRecords = async (value) => { - setPrimaryAuthorLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { or: [{ name: { contains: value } }] }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listAuthors.replaceAll(\\"__typename\\", \\"\\"), - variables, - }) - )?.data?.listAuthors?.items; - var loaded = result.filter( - (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); - setPrimaryAuthorLoading(false); - }; - const fetchPrimaryTitleRecords = async (value) => { - setPrimaryTitleLoading(true); + const fetchParentTableRecords = async (value) => { + setParentTableLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { or: [{ name: { contains: value } }] }, + filter: { + or: [{ name: { contains: value } }, { id: { contains: value } }], + }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listTitles.replaceAll(\\"__typename\\", \\"\\"), + query: listParentTables.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listTitles?.items; + )?.data?.listParentTables?.items; var loaded = result.filter( - (item) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(item)) + (item) => !parentTableIdSet.has(getIDValue.parentTable?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setPrimaryTitleRecords(newOptions.slice(0, autocompleteLength)); - setPrimaryTitleLoading(false); + setParentTableRecords(newOptions.slice(0, autocompleteLength)); + setParentTableLoading(false); }; React.useEffect(() => { - fetchPrimaryAuthorRecords(\\"\\"); - fetchPrimaryTitleRecords(\\"\\"); + fetchParentTableRecords(\\"\\"); }, []); return ( <Grid @@ -5107,8 +7796,8 @@ export default function BookCreateForm(props) { event.preventDefault(); let modelFields = { name, - primaryAuthor, - primaryTitle, + nmTest, + parentTable, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -5148,11 +7837,13 @@ export default function BookCreateForm(props) { }); const modelFieldsToSave = { name: modelFields.name, - authorId: modelFields?.primaryAuthor?.id, - titleId: modelFields?.primaryTitle?.id, + parentTableBasicTablesId: modelFields?.parentTable?.id, + nmTest: modelFields.nmTest + ? JSON.parse(modelFields.nmTest) + : modelFields.nmTest, }; await API.graphql({ - query: createBook.replaceAll(\\"__typename\\", \\"\\"), + query: createBasicTable.replaceAll(\\"__typename\\", \\"\\"), variables: { input: { ...modelFieldsToSave, @@ -5172,43 +7863,9 @@ export default function BookCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"BookCreateForm\\")} + {...getOverrideProps(overrides, \\"CreateForm\\")} {...rest} > - <Flex - justifyContent=\\"space-between\\" - {...getOverrideProps(overrides, \\"CTAFlex\\")} - > - <Button - children=\\"Clear\\" - type=\\"reset\\" - onClick={(event) => { - event.preventDefault(); - resetStateValues(); - }} - {...getOverrideProps(overrides, \\"ClearButton\\")} - ></Button> - <Flex - gap=\\"15px\\" - {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} - > - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> - <Button - children=\\"Submit\\" - type=\\"submit\\" - variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e?.hasError)} - {...getOverrideProps(overrides, \\"SubmitButton\\")} - ></Button> - </Flex> - </Flex> <TextField label=\\"Name\\" isRequired={false} @@ -5219,8 +7876,8 @@ export default function BookCreateForm(props) { if (onChange) { const modelFields = { name: value, - primaryAuthor, - primaryTitle, + nmTest, + parentTable, }; const result = onChange(modelFields); value = result?.name ?? value; @@ -5235,92 +7892,31 @@ export default function BookCreateForm(props) { hasError={errors.name?.hasError} {...getOverrideProps(overrides, \\"name\\")} ></TextField> - <ArrayField - lengthLimit={1} - onChange={async (items) => { - let value = items[0]; + <TextAreaField + label=\\"Nm test\\" + isRequired={false} + isReadOnly={false} + onChange={(e) => { + let { value } = e.target; if (onChange) { const modelFields = { name, - primaryAuthor: value, - primaryTitle, + nmTest: value, + parentTable, }; const result = onChange(modelFields); - value = result?.primaryAuthor ?? value; + value = result?.nmTest ?? value; } - setPrimaryAuthor(value); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); - }} - currentFieldValue={currentPrimaryAuthorValue} - label={\\"Primary author\\"} - items={primaryAuthor ? [primaryAuthor] : []} - hasError={errors?.primaryAuthor?.hasError} - runValidationTasks={async () => - await runValidationTasks(\\"primaryAuthor\\", currentPrimaryAuthorValue) - } - errorMessage={errors?.primaryAuthor?.errorMessage} - getBadgeText={getDisplayValue.primaryAuthor} - setFieldValue={(model) => { - setCurrentPrimaryAuthorDisplayValue( - model ? getDisplayValue.primaryAuthor(model) : \\"\\" - ); - setCurrentPrimaryAuthorValue(model); - }} - inputFieldRef={primaryAuthorRef} - defaultFieldValue={\\"\\"} - > - <Autocomplete - label=\\"Primary author\\" - isRequired={false} - isReadOnly={false} - placeholder=\\"Search Author\\" - value={currentPrimaryAuthorDisplayValue} - options={primaryAuthorRecords - .filter( - (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) - ) - .map((r) => ({ - id: getIDValue.primaryAuthor?.(r), - label: getDisplayValue.primaryAuthor?.(r), - }))} - isLoading={primaryAuthorLoading} - onSelect={({ id, label }) => { - setCurrentPrimaryAuthorValue( - primaryAuthorRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentPrimaryAuthorDisplayValue(label); - runValidationTasks(\\"primaryAuthor\\", label); - }} - onClear={() => { - setCurrentPrimaryAuthorDisplayValue(\\"\\"); - }} - onChange={(e) => { - let { value } = e.target; - fetchPrimaryAuthorRecords(value); - if (errors.primaryAuthor?.hasError) { - runValidationTasks(\\"primaryAuthor\\", value); - } - setCurrentPrimaryAuthorDisplayValue(value); - setCurrentPrimaryAuthorValue(undefined); - }} - onBlur={() => - runValidationTasks( - \\"primaryAuthor\\", - currentPrimaryAuthorDisplayValue - ) + if (errors.nmTest?.hasError) { + runValidationTasks(\\"nmTest\\", value); } - errorMessage={errors.primaryAuthor?.errorMessage} - hasError={errors.primaryAuthor?.hasError} - ref={primaryAuthorRef} - labelHidden={true} - {...getOverrideProps(overrides, \\"primaryAuthor\\")} - ></Autocomplete> - </ArrayField> + setNmTest(value); + }} + onBlur={() => runValidationTasks(\\"nmTest\\", nmTest)} + errorMessage={errors.nmTest?.errorMessage} + hasError={errors.nmTest?.hasError} + {...getOverrideProps(overrides, \\"nmTest\\")} + ></TextAreaField> <ArrayField lengthLimit={1} onChange={async (items) => { @@ -5328,129 +7924,154 @@ export default function BookCreateForm(props) { if (onChange) { const modelFields = { name, - primaryAuthor, - primaryTitle: value, + nmTest, + parentTable: value, }; const result = onChange(modelFields); - value = result?.primaryTitle ?? value; + value = result?.parentTable ?? value; } - setPrimaryTitle(value); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(\\"\\"); - }} - currentFieldValue={currentPrimaryTitleValue} - label={\\"Primary title\\"} - items={primaryTitle ? [primaryTitle] : []} - hasError={errors?.primaryTitle?.hasError} + setParentTable(value); + setCurrentParentTableValue(undefined); + setCurrentParentTableDisplayValue(\\"\\"); + }} + currentFieldValue={currentParentTableValue} + label={\\"Parent table\\"} + items={parentTable ? [parentTable] : []} + hasError={errors?.parentTable?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleValue) + await runValidationTasks(\\"parentTable\\", currentParentTableValue) } - errorMessage={errors?.primaryTitle?.errorMessage} - getBadgeText={getDisplayValue.primaryTitle} + errorMessage={errors?.parentTable?.errorMessage} + getBadgeText={getDisplayValue.parentTable} setFieldValue={(model) => { - setCurrentPrimaryTitleDisplayValue( - model ? getDisplayValue.primaryTitle(model) : \\"\\" + setCurrentParentTableDisplayValue( + model ? getDisplayValue.parentTable(model) : \\"\\" ); - setCurrentPrimaryTitleValue(model); + setCurrentParentTableValue(model); }} - inputFieldRef={primaryTitleRef} + inputFieldRef={parentTableRef} defaultFieldValue={\\"\\"} > <Autocomplete - label=\\"Primary title\\" + label=\\"Parent table\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search Title\\" - value={currentPrimaryTitleDisplayValue} - options={primaryTitleRecords - .filter((r) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(r))) + placeholder=\\"Search ParentTable\\" + value={currentParentTableDisplayValue} + options={parentTableRecords + .filter((r) => !parentTableIdSet.has(getIDValue.parentTable?.(r))) .map((r) => ({ - id: getIDValue.primaryTitle?.(r), - label: getDisplayValue.primaryTitle?.(r), + id: getIDValue.parentTable?.(r), + label: getDisplayValue.parentTable?.(r), }))} - isLoading={primaryTitleLoading} + isLoading={parentTableLoading} onSelect={({ id, label }) => { - setCurrentPrimaryTitleValue( - primaryTitleRecords.find((r) => + setCurrentParentTableValue( + parentTableRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentPrimaryTitleDisplayValue(label); - runValidationTasks(\\"primaryTitle\\", label); + setCurrentParentTableDisplayValue(label); + runValidationTasks(\\"parentTable\\", label); }} onClear={() => { - setCurrentPrimaryTitleDisplayValue(\\"\\"); + setCurrentParentTableDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchPrimaryTitleRecords(value); - if (errors.primaryTitle?.hasError) { - runValidationTasks(\\"primaryTitle\\", value); + fetchParentTableRecords(value); + if (errors.parentTable?.hasError) { + runValidationTasks(\\"parentTable\\", value); } - setCurrentPrimaryTitleDisplayValue(value); - setCurrentPrimaryTitleValue(undefined); + setCurrentParentTableDisplayValue(value); + setCurrentParentTableValue(undefined); }} onBlur={() => - runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleDisplayValue) + runValidationTasks(\\"parentTable\\", currentParentTableDisplayValue) } - errorMessage={errors.primaryTitle?.errorMessage} - hasError={errors.primaryTitle?.hasError} - ref={primaryTitleRef} + errorMessage={errors.parentTable?.errorMessage} + hasError={errors.parentTable?.hasError} + ref={parentTableRef} labelHidden={true} - {...getOverrideProps(overrides, \\"primaryTitle\\")} + {...getOverrideProps(overrides, \\"parentTable\\")} ></Autocomplete> </ArrayField> + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Clear\\" + type=\\"reset\\" + onClick={(event) => { + event.preventDefault(); + resetStateValues(); + }} + {...getOverrideProps(overrides, \\"ClearButton\\")} + ></Button> + <Flex + gap=\\"15px\\" + {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} + > + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={Object.values(errors).some((e) => e?.hasError)} + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> </Grid> ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with nonModel field 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Author, Title } from \\"../API\\"; +import { ParentTable } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type BookCreateFormInputValues = { +export declare type CreateFormInputValues = { name?: string; - primaryAuthor?: Author; - primaryTitle?: Title; + nmTest?: string; + parentTable?: ParentTable; }; -export declare type BookCreateFormValidationValues = { +export declare type CreateFormValidationValues = { name?: ValidationFunction<string>; - primaryAuthor?: ValidationFunction<Author>; - primaryTitle?: ValidationFunction<Title>; + nmTest?: ValidationFunction<string>; + parentTable?: ValidationFunction<ParentTable>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type BookCreateFormOverridesProps = { - BookCreateFormGrid?: PrimitiveOverrideProps<GridProps>; +export declare type CreateFormOverridesProps = { + CreateFormGrid?: PrimitiveOverrideProps<GridProps>; name?: PrimitiveOverrideProps<TextFieldProps>; - primaryAuthor?: PrimitiveOverrideProps<AutocompleteProps>; - primaryTitle?: PrimitiveOverrideProps<AutocompleteProps>; + nmTest?: PrimitiveOverrideProps<TextAreaFieldProps>; + parentTable?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type BookCreateFormProps = React.PropsWithChildren<{ - overrides?: BookCreateFormOverridesProps | undefined | null; +export declare type CreateFormProps = React.PropsWithChildren<{ + overrides?: CreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onSuccess?: (fields: BookCreateFormInputValues) => void; - onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onValidate?: BookCreateFormValidationValues; + onSubmit?: (fields: CreateFormInputValues) => CreateFormInputValues; + onSuccess?: (fields: CreateFormInputValues) => void; + onError?: (fields: CreateFormInputValues, errorMessage: string) => void; + onChange?: (fields: CreateFormInputValues) => CreateFormInputValues; + onValidate?: CreateFormValidationValues; } & React.CSSProperties>; -export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; +export default function CreateForm(props: CreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple relationship & cpk 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -5467,19 +8088,10 @@ import { useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; -import { CPKClass } from \\"../API\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { - listCPKClasses, - listCPKProjects, - listCPKStudents, -} from \\"../graphql/queries\\"; -import { - createCPKTeacher, - createCPKTeacherCPKClass, - updateCPKProject, -} from \\"../graphql/mutations\\"; +import { getPost, listComments } from \\"../graphql/queries\\"; +import { updateComment, updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -5635,9 +8247,10 @@ function ArrayField({ </React.Fragment> ); } -export default function CreateCPKTeacherForm(props) { +export default function PostUpdateForm(props) { const { - clearOnSuccess = true, + id: idProp, + post: postModelProp, onSuccess, onError, onSubmit, @@ -5647,87 +8260,74 @@ export default function CreateCPKTeacherForm(props) { ...rest } = props; const initialValues = { - specialTeacherId: \\"\\", - CPKStudent: undefined, - CPKClasses: [], - CPKProjects: [], + title: \\"\\", + body: \\"\\", + publishDate: \\"\\", + Comments: [], }; - const [specialTeacherId, setSpecialTeacherId] = React.useState( - initialValues.specialTeacherId - ); - const [CPKStudent, setCPKStudent] = React.useState(initialValues.CPKStudent); - const [CPKStudentLoading, setCPKStudentLoading] = React.useState(false); - const [CPKStudentRecords, setCPKStudentRecords] = React.useState([]); - const [CPKClasses, setCPKClasses] = React.useState(initialValues.CPKClasses); - const [CPKClassesLoading, setCPKClassesLoading] = React.useState(false); - const [CPKClassesRecords, setCPKClassesRecords] = React.useState([]); - const [CPKProjects, setCPKProjects] = React.useState( - initialValues.CPKProjects + const [title, setTitle] = React.useState(initialValues.title); + const [body, setBody] = React.useState(initialValues.body); + const [publishDate, setPublishDate] = React.useState( + initialValues.publishDate ); - const [CPKProjectsLoading, setCPKProjectsLoading] = React.useState(false); - const [CPKProjectsRecords, setCPKProjectsRecords] = React.useState([]); + const [Comments, setComments] = React.useState(initialValues.Comments); + const [CommentsLoading, setCommentsLoading] = React.useState(false); + const [CommentsRecords, setCommentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setSpecialTeacherId(initialValues.specialTeacherId); - setCPKStudent(initialValues.CPKStudent); - setCurrentCPKStudentValue(undefined); - setCurrentCPKStudentDisplayValue(\\"\\"); - setCPKClasses(initialValues.CPKClasses); - setCurrentCPKClassesValue(undefined); - setCurrentCPKClassesDisplayValue(\\"\\"); - setCPKProjects(initialValues.CPKProjects); - setCurrentCPKProjectsValue(undefined); - setCurrentCPKProjectsDisplayValue(\\"\\"); + const cleanValues = postRecord + ? { ...initialValues, ...postRecord, Comments: linkedComments } + : initialValues; + setTitle(cleanValues.title); + setBody(cleanValues.body); + setPublishDate(cleanValues.publishDate); + setComments(cleanValues.Comments ?? []); + setCurrentCommentsValue(undefined); + setCurrentCommentsDisplayValue(\\"\\"); setErrors({}); }; - const [currentCPKStudentDisplayValue, setCurrentCPKStudentDisplayValue] = - React.useState(\\"\\"); - const [currentCPKStudentValue, setCurrentCPKStudentValue] = - React.useState(undefined); - const CPKStudentRef = React.createRef(); - const [currentCPKClassesDisplayValue, setCurrentCPKClassesDisplayValue] = - React.useState(\\"\\"); - const [currentCPKClassesValue, setCurrentCPKClassesValue] = - React.useState(undefined); - const CPKClassesRef = React.createRef(); - const [currentCPKProjectsDisplayValue, setCurrentCPKProjectsDisplayValue] = - React.useState(\\"\\"); - const [currentCPKProjectsValue, setCurrentCPKProjectsValue] = - React.useState(undefined); - const CPKProjectsRef = React.createRef(); - const getIDValue = { - CPKStudent: (r) => - JSON.stringify({ specialStudentId: r?.specialStudentId }), - CPKClasses: (r) => JSON.stringify({ specialClassId: r?.specialClassId }), - CPKProjects: (r) => - JSON.stringify({ specialProjectId: r?.specialProjectId }), - }; - const CPKStudentIdSet = new Set( - Array.isArray(CPKStudent) - ? CPKStudent.map((r) => getIDValue.CPKStudent?.(r)) - : getIDValue.CPKStudent?.(CPKStudent) - ); - const CPKClassesIdSet = new Set( - Array.isArray(CPKClasses) - ? CPKClasses.map((r) => getIDValue.CPKClasses?.(r)) - : getIDValue.CPKClasses?.(CPKClasses) - ); - const CPKProjectsIdSet = new Set( - Array.isArray(CPKProjects) - ? CPKProjects.map((r) => getIDValue.CPKProjects?.(r)) - : getIDValue.CPKProjects?.(CPKProjects) + const [postRecord, setPostRecord] = React.useState(postModelProp); + const [linkedComments, setLinkedComments] = React.useState([]); + const canUnlinkComments = false; + React.useEffect(() => { + const queryData = async () => { + const record = idProp + ? ( + await API.graphql({ + query: getPost.replaceAll(\\"__typename\\", \\"\\"), + variables: { id: idProp }, + }) + )?.data?.getPost + : postModelProp; + const linkedComments = record?.Comments?.items ?? []; + setLinkedComments(linkedComments); + setPostRecord(record); + }; + queryData(); + }, [idProp, postModelProp]); + React.useEffect(resetStateValues, [postRecord, linkedComments]); + const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = + React.useState(\\"\\"); + const [currentCommentsValue, setCurrentCommentsValue] = + React.useState(undefined); + const CommentsRef = React.createRef(); + const getIDValue = { + Comments: (r) => JSON.stringify({ id: r?.id }), + }; + const CommentsIdSet = new Set( + Array.isArray(Comments) + ? Comments.map((r) => getIDValue.Comments?.(r)) + : getIDValue.Comments?.(Comments) ); const getDisplayValue = { - CPKStudent: (r) => r?.specialStudentId, - CPKClasses: (r) => r?.specialClassId, - CPKProjects: (r) => r?.specialProjectId, + Comments: (r) => \`\${r?.body ? r?.body + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { - specialTeacherId: [{ type: \\"Required\\" }], - CPKStudent: [], - CPKClasses: [], - CPKProjects: [], + title: [], + body: [], + publishDate: [], + Comments: [], }; const runValidationTasks = async ( fieldName, @@ -5746,91 +8346,54 @@ export default function CreateCPKTeacherForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchCPKStudentRecords = async (value) => { - setCPKStudentLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { or: [{ specialStudentId: { contains: value } }] }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listCPKStudents.replaceAll(\\"__typename\\", \\"\\"), - variables, - }) - )?.data?.listCPKStudents?.items; - var loaded = result.filter( - (item) => !CPKStudentIdSet.has(getIDValue.CPKStudent?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setCPKStudentRecords(newOptions.slice(0, autocompleteLength)); - setCPKStudentLoading(false); - }; - const fetchCPKClassesRecords = async (value) => { - setCPKClassesLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { or: [{ specialClassId: { contains: value } }] }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listCPKClasses.replaceAll(\\"__typename\\", \\"\\"), - variables, - }) - )?.data?.listCPKClasses?.items; - var loaded = result.filter( - (item) => !CPKClassesIdSet.has(getIDValue.CPKClasses?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setCPKClassesRecords(newOptions.slice(0, autocompleteLength)); - setCPKClassesLoading(false); + const convertToLocal = (date) => { + const df = new Intl.DateTimeFormat(\\"default\\", { + year: \\"numeric\\", + month: \\"2-digit\\", + day: \\"2-digit\\", + hour: \\"2-digit\\", + minute: \\"2-digit\\", + calendar: \\"iso8601\\", + numberingSystem: \\"latn\\", + hourCycle: \\"h23\\", + }); + const parts = df.formatToParts(date).reduce((acc, part) => { + acc[part.type] = part.value; + return acc; + }, {}); + return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; }; - const fetchCPKProjectsRecords = async (value) => { - setCPKProjectsLoading(true); + const fetchCommentsRecords = async (value) => { + setCommentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { or: [{ specialProjectId: { contains: value } }] }, + filter: { + or: [{ body: { contains: value } }, { id: { contains: value } }], + }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listCPKProjects.replaceAll(\\"__typename\\", \\"\\"), + query: listComments.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listCPKProjects?.items; + )?.data?.listComments?.items; var loaded = result.filter( - (item) => !CPKProjectsIdSet.has(getIDValue.CPKProjects?.(item)) + (item) => !CommentsIdSet.has(getIDValue.Comments?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setCPKProjectsRecords(newOptions.slice(0, autocompleteLength)); - setCPKProjectsLoading(false); + setCommentsRecords(newOptions.slice(0, autocompleteLength)); + setCommentsLoading(false); }; React.useEffect(() => { - fetchCPKStudentRecords(\\"\\"); - fetchCPKClassesRecords(\\"\\"); - fetchCPKProjectsRecords(\\"\\"); + fetchCommentsRecords(\\"\\"); }, []); return ( <Grid @@ -5841,10 +8404,10 @@ export default function CreateCPKTeacherForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - specialTeacherId, - CPKStudent, - CPKClasses, - CPKProjects, + title: title ?? null, + body: body ?? null, + publishDate: publishDate ?? null, + Comments: Comments ?? null, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -5882,61 +8445,76 @@ export default function CreateCPKTeacherForm(props) { modelFields[key] = null; } }); + const promises = []; + const commentsToLink = []; + const commentsToUnLink = []; + const commentsSet = new Set(); + const linkedCommentsSet = new Set(); + Comments.forEach((r) => commentsSet.add(getIDValue.Comments?.(r))); + linkedComments.forEach((r) => + linkedCommentsSet.add(getIDValue.Comments?.(r)) + ); + linkedComments.forEach((r) => { + if (!commentsSet.has(getIDValue.Comments?.(r))) { + commentsToUnLink.push(r); + } + }); + Comments.forEach((r) => { + if (!linkedCommentsSet.has(getIDValue.Comments?.(r))) { + commentsToLink.push(r); + } + }); + commentsToUnLink.forEach((original) => { + if (!canUnlinkComments) { + throw Error( + \`Comment \${original.id} cannot be unlinked from Post because postID is a required field.\` + ); + } + promises.push( + API.graphql({ + query: updateComment.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: original.id, + postID: null, + }, + }, + }) + ); + }); + commentsToLink.forEach((original) => { + promises.push( + API.graphql({ + query: updateComment.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: original.id, + postID: postRecord.id, + }, + }, + }) + ); + }); const modelFieldsToSave = { - specialTeacherId: modelFields.specialTeacherId, - cPKTeacherCPKStudentSpecialStudentId: - modelFields?.CPKStudent?.specialStudentId, + title: modelFields.title ?? null, + body: modelFields.body ?? null, + publishDate: modelFields.publishDate ?? null, }; - const cPKTeacher = ( - await API.graphql({ - query: createCPKTeacher.replaceAll(\\"__typename\\", \\"\\"), + promises.push( + API.graphql({ + query: updatePost.replaceAll(\\"__typename\\", \\"\\"), variables: { input: { + id: postRecord.id, ...modelFieldsToSave, }, }, }) - )?.data?.createCPKTeacher; - const promises = []; - promises.push( - ...CPKClasses.reduce((promises, cpkClass) => { - promises.push( - API.graphql({ - query: createCPKTeacherCPKClass.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - cPKTeacherSpecialTeacherId: cPKTeacher.specialTeacherId, - cPKClassSpecialClassId: CPKClass.specialClassId, - }, - }, - }) - ); - return promises; - }, []) - ); - promises.push( - ...CPKProjects.reduce((promises, original) => { - promises.push( - API.graphql({ - query: updateCPKProject.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - specialProjectId: original.specialProjectId, - cPKTeacherID: cPKTeacher.specialTeacherId, - }, - }, - }) - ); - return promises; - }, []) ); await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } - if (clearOnSuccess) { - resetStateValues(); - } } catch (err) { if (onError) { const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); @@ -5944,276 +8522,169 @@ export default function CreateCPKTeacherForm(props) { } } }} - {...getOverrideProps(overrides, \\"CreateCPKTeacherForm\\")} + {...getOverrideProps(overrides, \\"PostUpdateForm\\")} {...rest} > <TextField - label=\\"Special teacher id\\" - isRequired={true} + label=\\"Title\\" + isRequired={false} + isReadOnly={false} + value={title} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + title: value, + body, + publishDate, + Comments, + }; + const result = onChange(modelFields); + value = result?.title ?? value; + } + if (errors.title?.hasError) { + runValidationTasks(\\"title\\", value); + } + setTitle(value); + }} + onBlur={() => runValidationTasks(\\"title\\", title)} + errorMessage={errors.title?.errorMessage} + hasError={errors.title?.hasError} + {...getOverrideProps(overrides, \\"title\\")} + ></TextField> + <TextField + label=\\"Body\\" + isRequired={false} isReadOnly={false} - value={specialTeacherId} + value={body} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - specialTeacherId: value, - CPKStudent, - CPKClasses, - CPKProjects, + title, + body: value, + publishDate, + Comments, }; const result = onChange(modelFields); - value = result?.specialTeacherId ?? value; + value = result?.body ?? value; } - if (errors.specialTeacherId?.hasError) { - runValidationTasks(\\"specialTeacherId\\", value); + if (errors.body?.hasError) { + runValidationTasks(\\"body\\", value); } - setSpecialTeacherId(value); + setBody(value); }} - onBlur={() => runValidationTasks(\\"specialTeacherId\\", specialTeacherId)} - errorMessage={errors.specialTeacherId?.errorMessage} - hasError={errors.specialTeacherId?.hasError} - {...getOverrideProps(overrides, \\"specialTeacherId\\")} + onBlur={() => runValidationTasks(\\"body\\", body)} + errorMessage={errors.body?.errorMessage} + hasError={errors.body?.hasError} + {...getOverrideProps(overrides, \\"body\\")} ></TextField> - <ArrayField - lengthLimit={1} - onChange={async (items) => { - let value = items[0]; + <TextField + label=\\"Publish date\\" + isRequired={false} + isReadOnly={false} + type=\\"datetime-local\\" + value={publishDate && convertToLocal(new Date(publishDate))} + onChange={(e) => { + let value = + e.target.value === \\"\\" ? \\"\\" : new Date(e.target.value).toISOString(); if (onChange) { const modelFields = { - specialTeacherId, - CPKStudent: value, - CPKClasses, - CPKProjects, + title, + body, + publishDate: value, + Comments, }; const result = onChange(modelFields); - value = result?.CPKStudent ?? value; - } - setCPKStudent(value); - setCurrentCPKStudentValue(undefined); - setCurrentCPKStudentDisplayValue(\\"\\"); - }} - currentFieldValue={currentCPKStudentValue} - label={\\"Cpk student\\"} - items={CPKStudent ? [CPKStudent] : []} - hasError={errors?.CPKStudent?.hasError} - runValidationTasks={async () => - await runValidationTasks(\\"CPKStudent\\", currentCPKStudentValue) - } - errorMessage={errors?.CPKStudent?.errorMessage} - getBadgeText={getDisplayValue.CPKStudent} - setFieldValue={(model) => { - setCurrentCPKStudentDisplayValue( - model ? getDisplayValue.CPKStudent(model) : \\"\\" - ); - setCurrentCPKStudentValue(model); - }} - inputFieldRef={CPKStudentRef} - defaultFieldValue={\\"\\"} - > - <Autocomplete - label=\\"Cpk student\\" - isRequired={false} - isReadOnly={false} - placeholder=\\"Search CPKStudent\\" - value={currentCPKStudentDisplayValue} - options={CPKStudentRecords.filter( - (r) => !CPKStudentIdSet.has(getIDValue.CPKStudent?.(r)) - ).map((r) => ({ - id: getIDValue.CPKStudent?.(r), - label: getDisplayValue.CPKStudent?.(r), - }))} - isLoading={CPKStudentLoading} - onSelect={({ id, label }) => { - setCurrentCPKStudentValue( - CPKStudentRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentCPKStudentDisplayValue(label); - runValidationTasks(\\"CPKStudent\\", label); - }} - onClear={() => { - setCurrentCPKStudentDisplayValue(\\"\\"); - }} - onChange={(e) => { - let { value } = e.target; - fetchCPKStudentRecords(value); - if (errors.CPKStudent?.hasError) { - runValidationTasks(\\"CPKStudent\\", value); - } - setCurrentCPKStudentDisplayValue(value); - setCurrentCPKStudentValue(undefined); - }} - onBlur={() => - runValidationTasks(\\"CPKStudent\\", currentCPKStudentDisplayValue) + value = result?.publishDate ?? value; } - errorMessage={errors.CPKStudent?.errorMessage} - hasError={errors.CPKStudent?.hasError} - ref={CPKStudentRef} - labelHidden={true} - {...getOverrideProps(overrides, \\"CPKStudent\\")} - ></Autocomplete> - </ArrayField> - <ArrayField - onChange={async (items) => { - let values = items; - if (onChange) { - const modelFields = { - specialTeacherId, - CPKStudent, - CPKClasses: values, - CPKProjects, - }; - const result = onChange(modelFields); - values = result?.CPKClasses ?? values; + if (errors.publishDate?.hasError) { + runValidationTasks(\\"publishDate\\", value); } - setCPKClasses(values); - setCurrentCPKClassesValue(undefined); - setCurrentCPKClassesDisplayValue(\\"\\"); - }} - currentFieldValue={currentCPKClassesValue} - label={\\"Cpk classes\\"} - items={CPKClasses} - hasError={errors?.CPKClasses?.hasError} - runValidationTasks={async () => - await runValidationTasks(\\"CPKClasses\\", currentCPKClassesValue) - } - errorMessage={errors?.CPKClasses?.errorMessage} - getBadgeText={getDisplayValue.CPKClasses} - setFieldValue={(model) => { - setCurrentCPKClassesDisplayValue( - model ? getDisplayValue.CPKClasses(model) : \\"\\" - ); - setCurrentCPKClassesValue(model); + setPublishDate(value); }} - inputFieldRef={CPKClassesRef} - defaultFieldValue={\\"\\"} - > - <Autocomplete - label=\\"Cpk classes\\" - isRequired={false} - isReadOnly={false} - placeholder=\\"Search CPKClass\\" - value={currentCPKClassesDisplayValue} - options={CPKClassesRecords.map((r) => ({ - id: getIDValue.CPKClasses?.(r), - label: getDisplayValue.CPKClasses?.(r), - }))} - isLoading={CPKClassesLoading} - onSelect={({ id, label }) => { - setCurrentCPKClassesValue( - CPKClassesRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentCPKClassesDisplayValue(label); - runValidationTasks(\\"CPKClasses\\", label); - }} - onClear={() => { - setCurrentCPKClassesDisplayValue(\\"\\"); - }} - onChange={(e) => { - let { value } = e.target; - fetchCPKClassesRecords(value); - if (errors.CPKClasses?.hasError) { - runValidationTasks(\\"CPKClasses\\", value); - } - setCurrentCPKClassesDisplayValue(value); - setCurrentCPKClassesValue(undefined); - }} - onBlur={() => - runValidationTasks(\\"CPKClasses\\", currentCPKClassesDisplayValue) - } - errorMessage={errors.CPKClasses?.errorMessage} - hasError={errors.CPKClasses?.hasError} - ref={CPKClassesRef} - labelHidden={true} - {...getOverrideProps(overrides, \\"CPKClasses\\")} - ></Autocomplete> - </ArrayField> + onBlur={() => runValidationTasks(\\"publishDate\\", publishDate)} + errorMessage={errors.publishDate?.errorMessage} + hasError={errors.publishDate?.hasError} + {...getOverrideProps(overrides, \\"publishDate\\")} + ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { - specialTeacherId, - CPKStudent, - CPKClasses, - CPKProjects: values, + title, + body, + publishDate, + Comments: values, }; const result = onChange(modelFields); - values = result?.CPKProjects ?? values; + values = result?.Comments ?? values; } - setCPKProjects(values); - setCurrentCPKProjectsValue(undefined); - setCurrentCPKProjectsDisplayValue(\\"\\"); + setComments(values); + setCurrentCommentsValue(undefined); + setCurrentCommentsDisplayValue(\\"\\"); }} - currentFieldValue={currentCPKProjectsValue} - label={\\"Cpk projects\\"} - items={CPKProjects} - hasError={errors?.CPKProjects?.hasError} + currentFieldValue={currentCommentsValue} + label={\\"Comments\\"} + items={Comments} + hasError={errors?.Comments?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"CPKProjects\\", currentCPKProjectsValue) + await runValidationTasks(\\"Comments\\", currentCommentsValue) } - errorMessage={errors?.CPKProjects?.errorMessage} - getBadgeText={getDisplayValue.CPKProjects} + errorMessage={errors?.Comments?.errorMessage} + getBadgeText={getDisplayValue.Comments} setFieldValue={(model) => { - setCurrentCPKProjectsDisplayValue( - model ? getDisplayValue.CPKProjects(model) : \\"\\" + setCurrentCommentsDisplayValue( + model ? getDisplayValue.Comments(model) : \\"\\" ); - setCurrentCPKProjectsValue(model); + setCurrentCommentsValue(model); }} - inputFieldRef={CPKProjectsRef} + inputFieldRef={CommentsRef} defaultFieldValue={\\"\\"} > <Autocomplete - label=\\"Cpk projects\\" + label=\\"Comments\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search CPKProject\\" - value={currentCPKProjectsDisplayValue} - options={CPKProjectsRecords.filter( - (r) => !CPKProjectsIdSet.has(getIDValue.CPKProjects?.(r)) - ).map((r) => ({ - id: getIDValue.CPKProjects?.(r), - label: getDisplayValue.CPKProjects?.(r), + placeholder=\\"Search Comment\\" + value={currentCommentsDisplayValue} + options={CommentsRecords.map((r) => ({ + id: getIDValue.Comments?.(r), + label: getDisplayValue.Comments?.(r), }))} - isLoading={CPKProjectsLoading} + isLoading={CommentsLoading} onSelect={({ id, label }) => { - setCurrentCPKProjectsValue( - CPKProjectsRecords.find((r) => + setCurrentCommentsValue( + CommentsRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentCPKProjectsDisplayValue(label); - runValidationTasks(\\"CPKProjects\\", label); + setCurrentCommentsDisplayValue(label); + runValidationTasks(\\"Comments\\", label); }} onClear={() => { - setCurrentCPKProjectsDisplayValue(\\"\\"); + setCurrentCommentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchCPKProjectsRecords(value); - if (errors.CPKProjects?.hasError) { - runValidationTasks(\\"CPKProjects\\", value); + fetchCommentsRecords(value); + if (errors.Comments?.hasError) { + runValidationTasks(\\"Comments\\", value); } - setCurrentCPKProjectsDisplayValue(value); - setCurrentCPKProjectsValue(undefined); + setCurrentCommentsDisplayValue(value); + setCurrentCommentsValue(undefined); }} onBlur={() => - runValidationTasks(\\"CPKProjects\\", currentCPKProjectsDisplayValue) + runValidationTasks(\\"Comments\\", currentCommentsDisplayValue) } - errorMessage={errors.CPKProjects?.errorMessage} - hasError={errors.CPKProjects?.hasError} - ref={CPKProjectsRef} + errorMessage={errors.Comments?.errorMessage} + hasError={errors.Comments?.hasError} + ref={CommentsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"CPKProjects\\")} + {...getOverrideProps(overrides, \\"Comments\\")} ></Autocomplete> </ArrayField> <Flex @@ -6221,13 +8692,14 @@ export default function CreateCPKTeacherForm(props) { {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button - children=\\"Clear\\" + children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} - {...getOverrideProps(overrides, \\"ClearButton\\")} + isDisabled={!(idProp || postModelProp)} + {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" @@ -6237,7 +8709,10 @@ export default function CreateCPKTeacherForm(props) { children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e?.hasError)} + isDisabled={ + !(idProp || postModelProp) || + Object.values(errors).some((e) => e?.hasError) + } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> @@ -6248,55 +8723,55 @@ export default function CreateCPKTeacherForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple relationship & cpk 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { CPKClass, CPKProject, CPKStudent } from \\"../API\\"; +import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type CreateCPKTeacherFormInputValues = { - specialTeacherId?: string; - CPKStudent?: CPKStudent; - CPKClasses?: CPKClass[]; - CPKProjects?: CPKProject[]; +export declare type PostUpdateFormInputValues = { + title?: string; + body?: string; + publishDate?: string; + Comments?: Comment[]; }; -export declare type CreateCPKTeacherFormValidationValues = { - specialTeacherId?: ValidationFunction<string>; - CPKStudent?: ValidationFunction<CPKStudent>; - CPKClasses?: ValidationFunction<CPKClass>; - CPKProjects?: ValidationFunction<CPKProject>; +export declare type PostUpdateFormValidationValues = { + title?: ValidationFunction<string>; + body?: ValidationFunction<string>; + publishDate?: ValidationFunction<string>; + Comments?: ValidationFunction<Comment>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type CreateCPKTeacherFormOverridesProps = { - CreateCPKTeacherFormGrid?: PrimitiveOverrideProps<GridProps>; - specialTeacherId?: PrimitiveOverrideProps<TextFieldProps>; - CPKStudent?: PrimitiveOverrideProps<AutocompleteProps>; - CPKClasses?: PrimitiveOverrideProps<AutocompleteProps>; - CPKProjects?: PrimitiveOverrideProps<AutocompleteProps>; +export declare type PostUpdateFormOverridesProps = { + PostUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; + title?: PrimitiveOverrideProps<TextFieldProps>; + body?: PrimitiveOverrideProps<TextFieldProps>; + publishDate?: PrimitiveOverrideProps<TextFieldProps>; + Comments?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type CreateCPKTeacherFormProps = React.PropsWithChildren<{ - overrides?: CreateCPKTeacherFormOverridesProps | undefined | null; +export declare type PostUpdateFormProps = React.PropsWithChildren<{ + overrides?: PostUpdateFormOverridesProps | undefined | null; } & { - clearOnSuccess?: boolean; - onSubmit?: (fields: CreateCPKTeacherFormInputValues) => CreateCPKTeacherFormInputValues; - onSuccess?: (fields: CreateCPKTeacherFormInputValues) => void; - onError?: (fields: CreateCPKTeacherFormInputValues, errorMessage: string) => void; - onChange?: (fields: CreateCPKTeacherFormInputValues) => CreateCPKTeacherFormInputValues; - onValidate?: CreateCPKTeacherFormValidationValues; + id?: string; + post?: Post; + onSubmit?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; + onSuccess?: (fields: PostUpdateFormInputValues) => void; + onError?: (fields: PostUpdateFormInputValues, errorMessage: string) => void; + onChange?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; + onValidate?: PostUpdateFormValidationValues; } & React.CSSProperties>; -export default function CreateCPKTeacherForm(props: CreateCPKTeacherFormProps): React.ReactElement; +export default function PostUpdateForm(props: PostUpdateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with nonModel field 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships - amplify js v6 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { - Autocomplete, Badge, Button, Divider, @@ -6311,9 +8786,10 @@ import { } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { API } from \\"aws-amplify\\"; -import { listParentTables } from \\"../graphql/queries\\"; -import { createBasicTable } from \\"../graphql/mutations\\"; +import { generateClient } from \\"aws-amplify/api\\"; +import { getPost } from \\"../graphql/queries\\"; +import { updatePost } from \\"../graphql/mutations\\"; +const client = generateClient(); function ArrayField({ items = [], onChange, @@ -6469,109 +8945,120 @@ function ArrayField({ </React.Fragment> ); } -export default function CreateForm(props) { +export default function MyPostForm(props) { const { - clearOnSuccess = true, + id: idProp, + post: postModelProp, onSuccess, onError, onSubmit, + onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - name: \\"\\", - nmTest: \\"\\", - parentTable: undefined, + TextAreaFieldbbd63464: \\"\\", + caption: \\"\\", + username: \\"\\", + profile_url: \\"\\", + post_url: \\"\\", + metadata: \\"\\", + nonModelField: \\"\\", + nonModelFieldArray: [], }; - const [name, setName] = React.useState(initialValues.name); - const [nmTest, setNmTest] = React.useState(initialValues.nmTest); - const [parentTable, setParentTable] = React.useState( - initialValues.parentTable + const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( + initialValues.TextAreaFieldbbd63464 + ); + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState(initialValues.metadata); + const [nonModelField, setNonModelField] = React.useState( + initialValues.nonModelField + ); + const [nonModelFieldArray, setNonModelFieldArray] = React.useState( + initialValues.nonModelFieldArray ); - const [parentTableLoading, setParentTableLoading] = React.useState(false); - const [parentTableRecords, setParentTableRecords] = React.useState([]); - const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setName(initialValues.name); - setNmTest(initialValues.nmTest); - setParentTable(initialValues.parentTable); - setCurrentParentTableValue(undefined); - setCurrentParentTableDisplayValue(\\"\\"); + const cleanValues = postRecord + ? { ...initialValues, ...postRecord } + : initialValues; + setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); + setCaption(cleanValues.caption); + setUsername(cleanValues.username); + setProfile_url(cleanValues.profile_url); + setPost_url(cleanValues.post_url); + setMetadata( + typeof cleanValues.metadata === \\"string\\" || cleanValues.metadata === null + ? cleanValues.metadata + : JSON.stringify(cleanValues.metadata) + ); + setNonModelField( + typeof cleanValues.nonModelField === \\"string\\" || + cleanValues.nonModelField === null + ? cleanValues.nonModelField + : JSON.stringify(cleanValues.nonModelField) + ); + setNonModelFieldArray( + cleanValues.nonModelFieldArray?.map((item) => + typeof item === \\"string\\" ? item : JSON.stringify(item) + ) ?? [] + ); + setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; - const [currentParentTableDisplayValue, setCurrentParentTableDisplayValue] = + const [postRecord, setPostRecord] = React.useState(postModelProp); + React.useEffect(() => { + const queryData = async () => { + const record = idProp + ? ( + await client.graphql({ + query: getPost.replaceAll(\\"__typename\\", \\"\\"), + variables: { id: idProp }, + }) + )?.data?.getPost + : postModelProp; + setPostRecord(record); + }; + queryData(); + }, [idProp, postModelProp]); + React.useEffect(resetStateValues, [postRecord]); + const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); - const [currentParentTableValue, setCurrentParentTableValue] = - React.useState(undefined); - const parentTableRef = React.createRef(); - const getIDValue = { - parentTable: (r) => JSON.stringify({ id: r?.id }), - }; - const parentTableIdSet = new Set( - Array.isArray(parentTable) - ? parentTable.map((r) => getIDValue.parentTable?.(r)) - : getIDValue.parentTable?.(parentTable) - ); - const getDisplayValue = { - parentTable: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, - }; + const nonModelFieldArrayRef = React.createRef(); const validations = { - name: [], - nmTest: [{ type: \\"JSON\\" }], - parentTable: [], - }; - const runValidationTasks = async ( - fieldName, - currentValue, - getDisplayValue - ) => { - const value = - currentValue && getDisplayValue - ? getDisplayValue(currentValue) - : currentValue; - let validationResponse = validateField(value, validations[fieldName]); - const customValidator = fetchByPath(onValidate, fieldName); - if (customValidator) { - validationResponse = await customValidator(value, validationResponse); - } - setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); - return validationResponse; - }; - const fetchParentTableRecords = async (value) => { - setParentTableLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { - or: [{ name: { contains: value } }, { id: { contains: value } }], - }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listParentTables.replaceAll(\\"__typename\\", \\"\\"), - variables, - }) - )?.data?.listParentTables?.items; - var loaded = result.filter( - (item) => !parentTableIdSet.has(getIDValue.parentTable?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; + TextAreaFieldbbd63464: [], + caption: [], + username: [], + profile_url: [{ type: \\"URL\\" }], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], + nonModelField: [{ type: \\"JSON\\" }], + nonModelFieldArray: [{ type: \\"JSON\\" }], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); } - setParentTableRecords(newOptions.slice(0, autocompleteLength)); - setParentTableLoading(false); + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; }; - React.useEffect(() => { - fetchParentTableRecords(\\"\\"); - }, []); return ( <Grid as=\\"form\\" @@ -6581,30 +9068,27 @@ export default function CreateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - name, - nmTest, - parentTable, + TextAreaFieldbbd63464: TextAreaFieldbbd63464 ?? null, + caption: caption ?? null, + username: username ?? null, + profile_url: profile_url ?? null, + post_url: post_url ?? null, + metadata: metadata ?? null, + nonModelField: nonModelField ?? null, + nonModelFieldArray: nonModelFieldArray ?? null, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks( - fieldName, - item, - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, item) ) ); return promises; } promises.push( - runValidationTasks( - fieldName, - modelFields[fieldName], - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) @@ -6622,16 +9106,23 @@ export default function CreateForm(props) { } }); const modelFieldsToSave = { - name: modelFields.name, - parentTableBasicTablesId: modelFields?.parentTable?.id, - nmTest: modelFields.nmTest - ? JSON.parse(modelFields.nmTest) - : modelFields.nmTest, + caption: modelFields.caption ?? null, + username: modelFields.username ?? null, + profile_url: modelFields.profile_url ?? null, + post_url: modelFields.post_url ?? null, + metadata: modelFields.metadata ?? null, + nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => + JSON.parse(s) + ), + nonModelField: modelFields.nonModelField + ? JSON.parse(modelFields.nonModelField) + : modelFields.nonModelField, }; - await API.graphql({ - query: createBasicTable.replaceAll(\\"__typename\\", \\"\\"), + await client.graphql({ + query: updatePost.replaceAll(\\"__typename\\", \\"\\"), variables: { input: { + id: postRecord.id, ...modelFieldsToSave, }, }, @@ -6639,173 +9130,365 @@ export default function CreateForm(props) { if (onSuccess) { onSuccess(modelFields); } - if (clearOnSuccess) { + } catch (err) { + if (onError) { + const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); + onError(modelFields, messages); + } + } + }} + {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...rest} + > + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={(event) => { + event.preventDefault(); resetStateValues(); + }} + isDisabled={!(idProp || postModelProp)} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex + gap=\\"15px\\" + {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} + > + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={ + !(idProp || postModelProp) || + Object.values(errors).some((e) => e?.hasError) + } + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> + <TextAreaField + label=\\"Label\\" + value={TextAreaFieldbbd63464} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464: value, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.TextAreaFieldbbd63464 ?? value; + } + if (errors.TextAreaFieldbbd63464?.hasError) { + runValidationTasks(\\"TextAreaFieldbbd63464\\", value); + } + setTextAreaFieldbbd63464(value); + }} + onBlur={() => + runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) + } + errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} + hasError={errors.TextAreaFieldbbd63464?.hasError} + {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} + ></TextAreaField> + <TextField + label=\\"Caption\\" + isRequired={false} + isReadOnly={false} + value={caption} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption: value, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.caption ?? value; + } + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); + } + setCaption(value); + }} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} + ></TextField> + <TextField + label=\\"Username\\" + isRequired={false} + isReadOnly={false} + value={username} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username: value, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.username ?? value; + } + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); + } + setUsername(value); + }} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + ></TextField> + <TextField + label=\\"Profile url\\" + isRequired={false} + isReadOnly={false} + value={profile_url} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url: value, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + ></TextField> + <TextField + label=\\"Post url\\" + isRequired={false} + isReadOnly={false} + value={post_url} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url: value, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; } - } catch (err) { - if (onError) { - const messages = err.errors.map((e) => e.message).join(\\"\\\\n\\"); - onError(modelFields, messages); + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); } - } - }} - {...getOverrideProps(overrides, \\"CreateForm\\")} - {...rest} - > - <TextField - label=\\"Name\\" + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + ></TextField> + <TextAreaField + label=\\"Metadata\\" isRequired={false} isReadOnly={false} - value={name} + value={metadata} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - nmTest, - parentTable, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata: value, + nonModelField, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.name ?? value; + value = result?.metadata ?? value; } - if (errors.name?.hasError) { - runValidationTasks(\\"name\\", value); + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); } - setName(value); + setMetadata(value); }} - onBlur={() => runValidationTasks(\\"name\\", name)} - errorMessage={errors.name?.errorMessage} - hasError={errors.name?.hasError} - {...getOverrideProps(overrides, \\"name\\")} - ></TextField> + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + ></TextAreaField> <TextAreaField - label=\\"Nm test\\" + label=\\"Non model field\\" isRequired={false} isReadOnly={false} + value={nonModelField} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - name, - nmTest: value, - parentTable, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField: value, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.nmTest ?? value; + value = result?.nonModelField ?? value; } - if (errors.nmTest?.hasError) { - runValidationTasks(\\"nmTest\\", value); + if (errors.nonModelField?.hasError) { + runValidationTasks(\\"nonModelField\\", value); } - setNmTest(value); + setNonModelField(value); }} - onBlur={() => runValidationTasks(\\"nmTest\\", nmTest)} - errorMessage={errors.nmTest?.errorMessage} - hasError={errors.nmTest?.hasError} - {...getOverrideProps(overrides, \\"nmTest\\")} + onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} + errorMessage={errors.nonModelField?.errorMessage} + hasError={errors.nonModelField?.hasError} + {...getOverrideProps(overrides, \\"nonModelField\\")} ></TextAreaField> <ArrayField - lengthLimit={1} onChange={async (items) => { - let value = items[0]; + let values = items; if (onChange) { const modelFields = { - name, - nmTest, - parentTable: value, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray: values, }; const result = onChange(modelFields); - value = result?.parentTable ?? value; + values = result?.nonModelFieldArray ?? values; } - setParentTable(value); - setCurrentParentTableValue(undefined); - setCurrentParentTableDisplayValue(\\"\\"); + setNonModelFieldArray(values); + setCurrentNonModelFieldArrayValue(\\"\\"); }} - currentFieldValue={currentParentTableValue} - label={\\"Parent table\\"} - items={parentTable ? [parentTable] : []} - hasError={errors?.parentTable?.hasError} + currentFieldValue={currentNonModelFieldArrayValue} + label={\\"Non model field array\\"} + items={nonModelFieldArray} + hasError={errors?.nonModelFieldArray?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"parentTable\\", currentParentTableValue) + await runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) } - errorMessage={errors?.parentTable?.errorMessage} - getBadgeText={getDisplayValue.parentTable} - setFieldValue={(model) => { - setCurrentParentTableDisplayValue( - model ? getDisplayValue.parentTable(model) : \\"\\" - ); - setCurrentParentTableValue(model); - }} - inputFieldRef={parentTableRef} + errorMessage={errors?.nonModelFieldArray?.errorMessage} + setFieldValue={setCurrentNonModelFieldArrayValue} + inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > - <Autocomplete - label=\\"Parent table\\" + <TextAreaField + label=\\"Non model field array\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search ParentTable\\" - value={currentParentTableDisplayValue} - options={parentTableRecords - .filter((r) => !parentTableIdSet.has(getIDValue.parentTable?.(r))) - .map((r) => ({ - id: getIDValue.parentTable?.(r), - label: getDisplayValue.parentTable?.(r), - }))} - isLoading={parentTableLoading} - onSelect={({ id, label }) => { - setCurrentParentTableValue( - parentTableRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentParentTableDisplayValue(label); - runValidationTasks(\\"parentTable\\", label); - }} - onClear={() => { - setCurrentParentTableDisplayValue(\\"\\"); - }} + value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; - fetchParentTableRecords(value); - if (errors.parentTable?.hasError) { - runValidationTasks(\\"parentTable\\", value); + if (errors.nonModelFieldArray?.hasError) { + runValidationTasks(\\"nonModelFieldArray\\", value); } - setCurrentParentTableDisplayValue(value); - setCurrentParentTableValue(undefined); + setCurrentNonModelFieldArrayValue(value); }} onBlur={() => - runValidationTasks(\\"parentTable\\", currentParentTableDisplayValue) + runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) } - errorMessage={errors.parentTable?.errorMessage} - hasError={errors.parentTable?.hasError} - ref={parentTableRef} + errorMessage={errors.nonModelFieldArray?.errorMessage} + hasError={errors.nonModelFieldArray?.hasError} + ref={nonModelFieldArrayRef} labelHidden={true} - {...getOverrideProps(overrides, \\"parentTable\\")} - ></Autocomplete> + {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} + ></TextAreaField> </ArrayField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button - children=\\"Clear\\" + children=\\"Reset\\" type=\\"reset\\" onClick={(event) => { event.preventDefault(); resetStateValues(); }} - {...getOverrideProps(overrides, \\"ClearButton\\")} + isDisabled={!(idProp || postModelProp)} + {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e?.hasError)} + isDisabled={ + !(idProp || postModelProp) || + Object.values(errors).some((e) => e?.hasError) + } {...getOverrideProps(overrides, \\"SubmitButton\\")} ></Button> </Flex> @@ -6816,52 +9499,68 @@ export default function CreateForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with nonModel field 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships - amplify js v6 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { ParentTable } from \\"../API\\"; +import { Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type CreateFormInputValues = { - name?: string; - nmTest?: string; - parentTable?: ParentTable; +export declare type MyPostFormInputValues = { + TextAreaFieldbbd63464?: string; + caption?: string; + username?: string; + profile_url?: string; + post_url?: string; + metadata?: string; + nonModelField?: string; + nonModelFieldArray?: string[]; }; -export declare type CreateFormValidationValues = { - name?: ValidationFunction<string>; - nmTest?: ValidationFunction<string>; - parentTable?: ValidationFunction<ParentTable>; +export declare type MyPostFormValidationValues = { + TextAreaFieldbbd63464?: ValidationFunction<string>; + caption?: ValidationFunction<string>; + username?: ValidationFunction<string>; + profile_url?: ValidationFunction<string>; + post_url?: ValidationFunction<string>; + metadata?: ValidationFunction<string>; + nonModelField?: ValidationFunction<string>; + nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type CreateFormOverridesProps = { - CreateFormGrid?: PrimitiveOverrideProps<GridProps>; - name?: PrimitiveOverrideProps<TextFieldProps>; - nmTest?: PrimitiveOverrideProps<TextAreaFieldProps>; - parentTable?: PrimitiveOverrideProps<AutocompleteProps>; -} & EscapeHatchProps; -export declare type CreateFormProps = React.PropsWithChildren<{ - overrides?: CreateFormOverridesProps | undefined | null; -} & { - clearOnSuccess?: boolean; - onSubmit?: (fields: CreateFormInputValues) => CreateFormInputValues; - onSuccess?: (fields: CreateFormInputValues) => void; - onError?: (fields: CreateFormInputValues, errorMessage: string) => void; - onChange?: (fields: CreateFormInputValues) => CreateFormInputValues; - onValidate?: CreateFormValidationValues; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; + TextAreaFieldbbd63464?: PrimitiveOverrideProps<TextAreaFieldProps>; + caption?: PrimitiveOverrideProps<TextFieldProps>; + username?: PrimitiveOverrideProps<TextFieldProps>; + profile_url?: PrimitiveOverrideProps<TextFieldProps>; + post_url?: PrimitiveOverrideProps<TextFieldProps>; + metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; + nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; + nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; +} & EscapeHatchProps; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; +} & { + id?: string; + post?: Post; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; -export default function CreateForm(props: CreateFormProps): React.ReactElement; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { - Autocomplete, Badge, Button, Divider, @@ -6870,14 +9569,15 @@ import { Icon, ScrollView, Text, + TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { getPost, listComments } from \\"../graphql/queries\\"; -import { updateComment, updatePost } from \\"../graphql/mutations\\"; +import { getPost } from \\"../graphql/queries\\"; +import { updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -7033,49 +9733,75 @@ function ArrayField({ </React.Fragment> ); } -export default function PostUpdateForm(props) { +export default function MyPostForm(props) { const { id: idProp, post: postModelProp, onSuccess, onError, onSubmit, + onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - title: \\"\\", - body: \\"\\", - publishDate: \\"\\", - Comments: [], + TextAreaFieldbbd63464: \\"\\", + caption: \\"\\", + username: \\"\\", + profile_url: \\"\\", + post_url: \\"\\", + metadata: \\"\\", + nonModelField: \\"\\", + nonModelFieldArray: [], }; - const [title, setTitle] = React.useState(initialValues.title); - const [body, setBody] = React.useState(initialValues.body); - const [publishDate, setPublishDate] = React.useState( - initialValues.publishDate + const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( + initialValues.TextAreaFieldbbd63464 + ); + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState(initialValues.metadata); + const [nonModelField, setNonModelField] = React.useState( + initialValues.nonModelField + ); + const [nonModelFieldArray, setNonModelFieldArray] = React.useState( + initialValues.nonModelFieldArray ); - const [Comments, setComments] = React.useState(initialValues.Comments); - const [CommentsLoading, setCommentsLoading] = React.useState(false); - const [CommentsRecords, setCommentsRecords] = React.useState([]); - const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = postRecord - ? { ...initialValues, ...postRecord, Comments: linkedComments } + ? { ...initialValues, ...postRecord } : initialValues; - setTitle(cleanValues.title); - setBody(cleanValues.body); - setPublishDate(cleanValues.publishDate); - setComments(cleanValues.Comments ?? []); - setCurrentCommentsValue(undefined); - setCurrentCommentsDisplayValue(\\"\\"); + setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); + setCaption(cleanValues.caption); + setUsername(cleanValues.username); + setProfile_url(cleanValues.profile_url); + setPost_url(cleanValues.post_url); + setMetadata( + typeof cleanValues.metadata === \\"string\\" || cleanValues.metadata === null + ? cleanValues.metadata + : JSON.stringify(cleanValues.metadata) + ); + setNonModelField( + typeof cleanValues.nonModelField === \\"string\\" || + cleanValues.nonModelField === null + ? cleanValues.nonModelField + : JSON.stringify(cleanValues.nonModelField) + ); + setNonModelFieldArray( + cleanValues.nonModelFieldArray?.map((item) => + typeof item === \\"string\\" ? item : JSON.stringify(item) + ) ?? [] + ); + setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [postRecord, setPostRecord] = React.useState(postModelProp); - const [linkedComments, setLinkedComments] = React.useState([]); - const canUnlinkComments = false; React.useEffect(() => { const queryData = async () => { const record = idProp @@ -7086,34 +9812,23 @@ export default function PostUpdateForm(props) { }) )?.data?.getPost : postModelProp; - const linkedComments = record?.Comments?.items ?? []; - setLinkedComments(linkedComments); setPostRecord(record); }; queryData(); }, [idProp, postModelProp]); - React.useEffect(resetStateValues, [postRecord, linkedComments]); - const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = + React.useEffect(resetStateValues, [postRecord]); + const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); - const [currentCommentsValue, setCurrentCommentsValue] = - React.useState(undefined); - const CommentsRef = React.createRef(); - const getIDValue = { - Comments: (r) => JSON.stringify({ id: r?.id }), - }; - const CommentsIdSet = new Set( - Array.isArray(Comments) - ? Comments.map((r) => getIDValue.Comments?.(r)) - : getIDValue.Comments?.(Comments) - ); - const getDisplayValue = { - Comments: (r) => \`\${r?.body ? r?.body + \\" - \\" : \\"\\"}\${r?.id}\`, - }; + const nonModelFieldArrayRef = React.createRef(); const validations = { - title: [], - body: [], - publishDate: [], - Comments: [], + TextAreaFieldbbd63464: [], + caption: [], + username: [], + profile_url: [{ type: \\"URL\\" }], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], + nonModelField: [{ type: \\"JSON\\" }], + nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, @@ -7132,55 +9847,6 @@ export default function PostUpdateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const convertToLocal = (date) => { - const df = new Intl.DateTimeFormat(\\"default\\", { - year: \\"numeric\\", - month: \\"2-digit\\", - day: \\"2-digit\\", - hour: \\"2-digit\\", - minute: \\"2-digit\\", - calendar: \\"iso8601\\", - numberingSystem: \\"latn\\", - hourCycle: \\"h23\\", - }); - const parts = df.formatToParts(date).reduce((acc, part) => { - acc[part.type] = part.value; - return acc; - }, {}); - return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; - }; - const fetchCommentsRecords = async (value) => { - setCommentsLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { - or: [{ body: { contains: value } }, { id: { contains: value } }], - }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listComments.replaceAll(\\"__typename\\", \\"\\"), - variables, - }) - )?.data?.listComments?.items; - var loaded = result.filter( - (item) => !CommentsIdSet.has(getIDValue.Comments?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setCommentsRecords(newOptions.slice(0, autocompleteLength)); - setCommentsLoading(false); - }; - React.useEffect(() => { - fetchCommentsRecords(\\"\\"); - }, []); return ( <Grid as=\\"form\\" @@ -7190,114 +9856,65 @@ export default function PostUpdateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - title: title ?? null, - body: body ?? null, - publishDate: publishDate ?? null, - Comments: Comments ?? null, + TextAreaFieldbbd63464: TextAreaFieldbbd63464 ?? null, + caption: caption ?? null, + username: username ?? null, + profile_url: profile_url ?? null, + post_url: post_url ?? null, + metadata: metadata ?? null, + nonModelField: nonModelField ?? null, + nonModelFieldArray: nonModelFieldArray ?? null, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks( - fieldName, - item, - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, item) ) - ); - return promises; - } - promises.push( - runValidationTasks( - fieldName, - modelFields[fieldName], - getDisplayValue[fieldName] - ) - ); - return promises; - }, []) - ); - if (validationResponses.some((r) => r.hasError)) { - return; - } - if (onSubmit) { - modelFields = onSubmit(modelFields); - } - try { - Object.entries(modelFields).forEach(([key, value]) => { - if (typeof value === \\"string\\" && value === \\"\\") { - modelFields[key] = null; - } - }); - const promises = []; - const commentsToLink = []; - const commentsToUnLink = []; - const commentsSet = new Set(); - const linkedCommentsSet = new Set(); - Comments.forEach((r) => commentsSet.add(getIDValue.Comments?.(r))); - linkedComments.forEach((r) => - linkedCommentsSet.add(getIDValue.Comments?.(r)) - ); - linkedComments.forEach((r) => { - if (!commentsSet.has(getIDValue.Comments?.(r))) { - commentsToUnLink.push(r); - } - }); - Comments.forEach((r) => { - if (!linkedCommentsSet.has(getIDValue.Comments?.(r))) { - commentsToLink.push(r); - } - }); - commentsToUnLink.forEach((original) => { - if (!canUnlinkComments) { - throw Error( - \`Comment \${original.id} cannot be unlinked from Post because postID is a required field.\` - ); - } - promises.push( - API.graphql({ - query: updateComment.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - id: original.id, - postID: null, - }, - }, - }) - ); - }); - commentsToLink.forEach((original) => { + ); + return promises; + } promises.push( - API.graphql({ - query: updateComment.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - id: original.id, - postID: postRecord.id, - }, - }, - }) + runValidationTasks(fieldName, modelFields[fieldName]) ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value === \\"\\") { + modelFields[key] = null; + } }); const modelFieldsToSave = { - title: modelFields.title ?? null, - body: modelFields.body ?? null, - publishDate: modelFields.publishDate ?? null, + caption: modelFields.caption ?? null, + username: modelFields.username ?? null, + profile_url: modelFields.profile_url ?? null, + post_url: modelFields.post_url ?? null, + metadata: modelFields.metadata ?? null, + nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => + JSON.parse(s) + ), + nonModelField: modelFields.nonModelField + ? JSON.parse(modelFields.nonModelField) + : modelFields.nonModelField, }; - promises.push( - API.graphql({ - query: updatePost.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - id: postRecord.id, - ...modelFieldsToSave, - }, + await API.graphql({ + query: updatePost.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: postRecord.id, + ...modelFieldsToSave, }, - }) - ); - await Promise.all(promises); + }, + }); if (onSuccess) { onSuccess(modelFields); } @@ -7308,170 +9925,323 @@ export default function PostUpdateForm(props) { } } }} - {...getOverrideProps(overrides, \\"PostUpdateForm\\")} + {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={(event) => { + event.preventDefault(); + resetStateValues(); + }} + isDisabled={!(idProp || postModelProp)} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex + gap=\\"15px\\" + {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} + > + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={ + !(idProp || postModelProp) || + Object.values(errors).some((e) => e?.hasError) + } + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> + <TextAreaField + label=\\"Label\\" + value={TextAreaFieldbbd63464} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464: value, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.TextAreaFieldbbd63464 ?? value; + } + if (errors.TextAreaFieldbbd63464?.hasError) { + runValidationTasks(\\"TextAreaFieldbbd63464\\", value); + } + setTextAreaFieldbbd63464(value); + }} + onBlur={() => + runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) + } + errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} + hasError={errors.TextAreaFieldbbd63464?.hasError} + {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} + ></TextAreaField> <TextField - label=\\"Title\\" + label=\\"Caption\\" isRequired={false} isReadOnly={false} - value={title} + value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - title: value, - body, - publishDate, - Comments, + TextAreaFieldbbd63464, + caption: value, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.title ?? value; + value = result?.caption ?? value; } - if (errors.title?.hasError) { - runValidationTasks(\\"title\\", value); + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); } - setTitle(value); + setCaption(value); }} - onBlur={() => runValidationTasks(\\"title\\", title)} - errorMessage={errors.title?.errorMessage} - hasError={errors.title?.hasError} - {...getOverrideProps(overrides, \\"title\\")} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} ></TextField> <TextField - label=\\"Body\\" + label=\\"Username\\" + isRequired={false} + isReadOnly={false} + value={username} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username: value, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.username ?? value; + } + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); + } + setUsername(value); + }} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + ></TextField> + <TextField + label=\\"Profile url\\" + isRequired={false} + isReadOnly={false} + value={profile_url} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url: value, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + ></TextField> + <TextField + label=\\"Post url\\" + isRequired={false} + isReadOnly={false} + value={post_url} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url: value, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; + } + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + ></TextField> + <TextAreaField + label=\\"Metadata\\" isRequired={false} isReadOnly={false} - value={body} + value={metadata} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - title, - body: value, - publishDate, - Comments, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata: value, + nonModelField, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.body ?? value; + value = result?.metadata ?? value; } - if (errors.body?.hasError) { - runValidationTasks(\\"body\\", value); + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); } - setBody(value); + setMetadata(value); }} - onBlur={() => runValidationTasks(\\"body\\", body)} - errorMessage={errors.body?.errorMessage} - hasError={errors.body?.hasError} - {...getOverrideProps(overrides, \\"body\\")} - ></TextField> - <TextField - label=\\"Publish date\\" + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + ></TextAreaField> + <TextAreaField + label=\\"Non model field\\" isRequired={false} isReadOnly={false} - type=\\"datetime-local\\" - value={publishDate && convertToLocal(new Date(publishDate))} + value={nonModelField} onChange={(e) => { - let value = - e.target.value === \\"\\" ? \\"\\" : new Date(e.target.value).toISOString(); + let { value } = e.target; if (onChange) { const modelFields = { - title, - body, - publishDate: value, - Comments, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField: value, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.publishDate ?? value; + value = result?.nonModelField ?? value; } - if (errors.publishDate?.hasError) { - runValidationTasks(\\"publishDate\\", value); + if (errors.nonModelField?.hasError) { + runValidationTasks(\\"nonModelField\\", value); } - setPublishDate(value); + setNonModelField(value); }} - onBlur={() => runValidationTasks(\\"publishDate\\", publishDate)} - errorMessage={errors.publishDate?.errorMessage} - hasError={errors.publishDate?.hasError} - {...getOverrideProps(overrides, \\"publishDate\\")} - ></TextField> + onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} + errorMessage={errors.nonModelField?.errorMessage} + hasError={errors.nonModelField?.hasError} + {...getOverrideProps(overrides, \\"nonModelField\\")} + ></TextAreaField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { - title, - body, - publishDate, - Comments: values, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray: values, }; const result = onChange(modelFields); - values = result?.Comments ?? values; + values = result?.nonModelFieldArray ?? values; } - setComments(values); - setCurrentCommentsValue(undefined); - setCurrentCommentsDisplayValue(\\"\\"); + setNonModelFieldArray(values); + setCurrentNonModelFieldArrayValue(\\"\\"); }} - currentFieldValue={currentCommentsValue} - label={\\"Comments\\"} - items={Comments} - hasError={errors?.Comments?.hasError} + currentFieldValue={currentNonModelFieldArrayValue} + label={\\"Non model field array\\"} + items={nonModelFieldArray} + hasError={errors?.nonModelFieldArray?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"Comments\\", currentCommentsValue) + await runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) } - errorMessage={errors?.Comments?.errorMessage} - getBadgeText={getDisplayValue.Comments} - setFieldValue={(model) => { - setCurrentCommentsDisplayValue( - model ? getDisplayValue.Comments(model) : \\"\\" - ); - setCurrentCommentsValue(model); - }} - inputFieldRef={CommentsRef} + errorMessage={errors?.nonModelFieldArray?.errorMessage} + setFieldValue={setCurrentNonModelFieldArrayValue} + inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > - <Autocomplete - label=\\"Comments\\" + <TextAreaField + label=\\"Non model field array\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search Comment\\" - value={currentCommentsDisplayValue} - options={CommentsRecords.map((r) => ({ - id: getIDValue.Comments?.(r), - label: getDisplayValue.Comments?.(r), - }))} - isLoading={CommentsLoading} - onSelect={({ id, label }) => { - setCurrentCommentsValue( - CommentsRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentCommentsDisplayValue(label); - runValidationTasks(\\"Comments\\", label); - }} - onClear={() => { - setCurrentCommentsDisplayValue(\\"\\"); - }} + value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; - fetchCommentsRecords(value); - if (errors.Comments?.hasError) { - runValidationTasks(\\"Comments\\", value); + if (errors.nonModelFieldArray?.hasError) { + runValidationTasks(\\"nonModelFieldArray\\", value); } - setCurrentCommentsDisplayValue(value); - setCurrentCommentsValue(undefined); + setCurrentNonModelFieldArrayValue(value); }} onBlur={() => - runValidationTasks(\\"Comments\\", currentCommentsDisplayValue) + runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) } - errorMessage={errors.Comments?.errorMessage} - hasError={errors.Comments?.hasError} - ref={CommentsRef} + errorMessage={errors.nonModelFieldArray?.errorMessage} + hasError={errors.nonModelFieldArray?.hasError} + ref={nonModelFieldArrayRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Comments\\")} - ></Autocomplete> + {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} + ></TextAreaField> </ArrayField> <Flex justifyContent=\\"space-between\\" @@ -7491,6 +10261,14 @@ export default function PostUpdateForm(props) { gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> <Button children=\\"Submit\\" type=\\"submit\\" @@ -7509,55 +10287,69 @@ export default function PostUpdateForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Comment, Post } from \\"../API\\"; +import { Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type PostUpdateFormInputValues = { - title?: string; - body?: string; - publishDate?: string; - Comments?: Comment[]; +export declare type MyPostFormInputValues = { + TextAreaFieldbbd63464?: string; + caption?: string; + username?: string; + profile_url?: string; + post_url?: string; + metadata?: string; + nonModelField?: string; + nonModelFieldArray?: string[]; }; -export declare type PostUpdateFormValidationValues = { - title?: ValidationFunction<string>; - body?: ValidationFunction<string>; - publishDate?: ValidationFunction<string>; - Comments?: ValidationFunction<Comment>; +export declare type MyPostFormValidationValues = { + TextAreaFieldbbd63464?: ValidationFunction<string>; + caption?: ValidationFunction<string>; + username?: ValidationFunction<string>; + profile_url?: ValidationFunction<string>; + post_url?: ValidationFunction<string>; + metadata?: ValidationFunction<string>; + nonModelField?: ValidationFunction<string>; + nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type PostUpdateFormOverridesProps = { - PostUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; - title?: PrimitiveOverrideProps<TextFieldProps>; - body?: PrimitiveOverrideProps<TextFieldProps>; - publishDate?: PrimitiveOverrideProps<TextFieldProps>; - Comments?: PrimitiveOverrideProps<AutocompleteProps>; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; + TextAreaFieldbbd63464?: PrimitiveOverrideProps<TextAreaFieldProps>; + caption?: PrimitiveOverrideProps<TextFieldProps>; + username?: PrimitiveOverrideProps<TextFieldProps>; + profile_url?: PrimitiveOverrideProps<TextFieldProps>; + post_url?: PrimitiveOverrideProps<TextFieldProps>; + metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; + nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; + nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; } & EscapeHatchProps; -export declare type PostUpdateFormProps = React.PropsWithChildren<{ - overrides?: PostUpdateFormOverridesProps | undefined | null; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; } & { id?: string; post?: Post; - onSubmit?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; - onSuccess?: (fields: PostUpdateFormInputValues) => void; - onError?: (fields: PostUpdateFormInputValues, errorMessage: string) => void; - onChange?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; - onValidate?: PostUpdateFormValidationValues; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; -export default function PostUpdateForm(props: PostUpdateFormProps): React.ReactElement; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with composite primary key 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { + Autocomplete, Badge, Button, Divider, @@ -7566,15 +10358,23 @@ import { Icon, ScrollView, Text, - TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; +import { + getMovie, + listMovieTags, + listTags, + movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre, +} from \\"../graphql/queries\\"; import { API } from \\"aws-amplify\\"; -import { getPost } from \\"../graphql/queries\\"; -import { updatePost } from \\"../graphql/mutations\\"; +import { + createMovieTags, + deleteMovieTags, + updateMovie, +} from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -7730,102 +10530,105 @@ function ArrayField({ </React.Fragment> ); } -export default function MyPostForm(props) { +export default function MovieUpdateForm(props) { const { id: idProp, - post: postModelProp, + movie: movieModelProp, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - TextAreaFieldbbd63464: \\"\\", - caption: \\"\\", - username: \\"\\", - profile_url: \\"\\", - post_url: \\"\\", - metadata: \\"\\", - nonModelField: \\"\\", - nonModelFieldArray: [], + movieKey: \\"\\", + title: \\"\\", + genre: \\"\\", + rating: \\"\\", + tags: [], }; - const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( - initialValues.TextAreaFieldbbd63464 - ); - const [caption, setCaption] = React.useState(initialValues.caption); - const [username, setUsername] = React.useState(initialValues.username); - const [profile_url, setProfile_url] = React.useState( - initialValues.profile_url - ); - const [post_url, setPost_url] = React.useState(initialValues.post_url); - const [metadata, setMetadata] = React.useState(initialValues.metadata); - const [nonModelField, setNonModelField] = React.useState( - initialValues.nonModelField - ); - const [nonModelFieldArray, setNonModelFieldArray] = React.useState( - initialValues.nonModelFieldArray - ); + const [movieKey, setMovieKey] = React.useState(initialValues.movieKey); + const [title, setTitle] = React.useState(initialValues.title); + const [genre, setGenre] = React.useState(initialValues.genre); + const [rating, setRating] = React.useState(initialValues.rating); + const [tags, setTags] = React.useState(initialValues.tags); + const [tagsLoading, setTagsLoading] = React.useState(false); + const [tagsRecords, setTagsRecords] = React.useState([]); + const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - const cleanValues = postRecord - ? { ...initialValues, ...postRecord } + const cleanValues = movieRecord + ? { ...initialValues, ...movieRecord, tags: linkedTags } : initialValues; - setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); - setCaption(cleanValues.caption); - setUsername(cleanValues.username); - setProfile_url(cleanValues.profile_url); - setPost_url(cleanValues.post_url); - setMetadata( - typeof cleanValues.metadata === \\"string\\" || cleanValues.metadata === null - ? cleanValues.metadata - : JSON.stringify(cleanValues.metadata) - ); - setNonModelField( - typeof cleanValues.nonModelField === \\"string\\" || - cleanValues.nonModelField === null - ? cleanValues.nonModelField - : JSON.stringify(cleanValues.nonModelField) - ); - setNonModelFieldArray( - cleanValues.nonModelFieldArray?.map((item) => - typeof item === \\"string\\" ? item : JSON.stringify(item) - ) ?? [] - ); - setCurrentNonModelFieldArrayValue(\\"\\"); + setMovieKey(cleanValues.movieKey); + setTitle(cleanValues.title); + setGenre(cleanValues.genre); + setRating(cleanValues.rating); + setTags(cleanValues.tags ?? []); + setCurrentTagsValue(undefined); + setCurrentTagsDisplayValue(\\"\\"); setErrors({}); }; - const [postRecord, setPostRecord] = React.useState(postModelProp); + const [movieRecord, setMovieRecord] = React.useState(movieModelProp); + const [linkedTags, setLinkedTags] = React.useState([]); + const canUnlinkTags = false; React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ - query: getPost.replaceAll(\\"__typename\\", \\"\\"), - variables: { id: idProp }, + query: getMovie.replaceAll(\\"__typename\\", \\"\\"), + variables: { ...idProp }, }) - )?.data?.getPost - : postModelProp; - setPostRecord(record); + )?.data?.getMovie + : movieModelProp; + const linkedTags = record + ? ( + await API.graphql({ + query: + movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre.replaceAll( + \\"__typename\\", + \\"\\" + ), + variables: { + movieMovieKey: record.movieKey, + movietitle: record.title, + moviegenre: record.genre, + }, + }) + ).data.movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre.items.map( + (t) => t.tag + ) + : []; + setLinkedTags(linkedTags); + setMovieRecord(record); }; queryData(); - }, [idProp, postModelProp]); - React.useEffect(resetStateValues, [postRecord]); - const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = + }, [idProp, movieModelProp]); + React.useEffect(resetStateValues, [movieRecord, linkedTags]); + const [currentTagsDisplayValue, setCurrentTagsDisplayValue] = React.useState(\\"\\"); - const nonModelFieldArrayRef = React.createRef(); + const [currentTagsValue, setCurrentTagsValue] = React.useState(undefined); + const tagsRef = React.createRef(); + const getIDValue = { + tags: (r) => JSON.stringify({ id: r?.id }), + }; + const tagsIdSet = new Set( + Array.isArray(tags) + ? tags.map((r) => getIDValue.tags?.(r)) + : getIDValue.tags?.(tags) + ); + const getDisplayValue = { + tags: (r) => \`\${r?.label ? r?.label + \\" - \\" : \\"\\"}\${r?.id}\`, + }; const validations = { - TextAreaFieldbbd63464: [], - caption: [], - username: [], - profile_url: [{ type: \\"URL\\" }], - post_url: [{ type: \\"URL\\" }], - metadata: [{ type: \\"JSON\\" }], - nonModelField: [{ type: \\"JSON\\" }], - nonModelFieldArray: [{ type: \\"JSON\\" }], + movieKey: [{ type: \\"Required\\" }], + title: [{ type: \\"Required\\" }], + genre: [{ type: \\"Required\\" }], + rating: [], + tags: [], }; const runValidationTasks = async ( fieldName, @@ -7844,6 +10647,38 @@ export default function MyPostForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; + const fetchTagsRecords = async (value) => { + setTagsLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ label: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTags.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listTags?.items; + var loaded = result.filter( + (item) => !tagsIdSet.has(getIDValue.tags?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setTagsRecords(newOptions.slice(0, autocompleteLength)); + setTagsLoading(false); + }; + React.useEffect(() => { + fetchTagsRecords(\\"\\"); + }, []); return ( <Grid as=\\"form\\" @@ -7853,65 +10688,157 @@ export default function MyPostForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - TextAreaFieldbbd63464: TextAreaFieldbbd63464 ?? null, - caption: caption ?? null, - username: username ?? null, - profile_url: profile_url ?? null, - post_url: post_url ?? null, - metadata: metadata ?? null, - nonModelField: nonModelField ?? null, - nonModelFieldArray: nonModelFieldArray ?? null, + movieKey, + title, + genre, + rating: rating ?? null, + tags: tags ?? null, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( - ...modelFields[fieldName].map((item) => - runValidationTasks(fieldName, item) - ) + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value === \\"\\") { + modelFields[key] = null; + } + }); + const promises = []; + const tagsToLinkMap = new Map(); + const tagsToUnLinkMap = new Map(); + const tagsMap = new Map(); + const linkedTagsMap = new Map(); + tags.forEach((r) => { + const count = tagsMap.get(getIDValue.tags?.(r)); + const newCount = count ? count + 1 : 1; + tagsMap.set(getIDValue.tags?.(r), newCount); + }); + linkedTags.forEach((r) => { + const count = linkedTagsMap.get(getIDValue.tags?.(r)); + const newCount = count ? count + 1 : 1; + linkedTagsMap.set(getIDValue.tags?.(r), newCount); + }); + linkedTagsMap.forEach((count, id) => { + const newCount = tagsMap.get(id); + if (newCount) { + const diffCount = count - newCount; + if (diffCount > 0) { + tagsToUnLinkMap.set(id, diffCount); + } + } else { + tagsToUnLinkMap.set(id, count); + } + }); + tagsMap.forEach((count, id) => { + const originalCount = linkedTagsMap.get(id); + if (originalCount) { + const diffCount = count - originalCount; + if (diffCount > 0) { + tagsToLinkMap.set(id, diffCount); + } + } else { + tagsToLinkMap.set(id, count); + } + }); + tagsToUnLinkMap.forEach(async (count, id) => { + const recordKeys = JSON.parse(id); + const movieTagsRecords = ( + await API.graphql({ + query: listMovieTags.replaceAll(\\"__typename\\", \\"\\"), + variables: { + filter: { + and: [ + { tagId: { eq: recordKeys.id } }, + { movieMovieKey: { eq: movieRecord.movieKey } }, + { movietitle: { eq: movieRecord.title } }, + { moviegenre: { eq: movieRecord.genre } }, + ], + }, + }, + }) + )?.data?.listMovieTags?.items; + for (let i = 0; i < count; i++) { + promises.push( + API.graphql({ + query: deleteMovieTags.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: movieTagsRecords[i].id, + }, + }, + }) ); - return promises; } - promises.push( - runValidationTasks(fieldName, modelFields[fieldName]) + }); + tagsToLinkMap.forEach((count, id) => { + const tagToLink = tagRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) ); - return promises; - }, []) - ); - if (validationResponses.some((r) => r.hasError)) { - return; - } - if (onSubmit) { - modelFields = onSubmit(modelFields); - } - try { - Object.entries(modelFields).forEach(([key, value]) => { - if (typeof value === \\"string\\" && value === \\"\\") { - modelFields[key] = null; + for (let i = count; i > 0; i--) { + promises.push( + API.graphql({ + query: createMovieTags.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + movieMovieKey: movieRecord.movieKey, + movietitle: movieRecord.title, + moviegenre: movieRecord.genre, + tagId: tagToLink.id, + }, + }, + }) + ); } }); const modelFieldsToSave = { - caption: modelFields.caption ?? null, - username: modelFields.username ?? null, - profile_url: modelFields.profile_url ?? null, - post_url: modelFields.post_url ?? null, - metadata: modelFields.metadata ?? null, - nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => - JSON.parse(s) - ), - nonModelField: modelFields.nonModelField - ? JSON.parse(modelFields.nonModelField) - : modelFields.nonModelField, + movieKey: modelFields.movieKey, + title: modelFields.title, + genre: modelFields.genre, + rating: modelFields.rating ?? null, }; - await API.graphql({ - query: updatePost.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - id: postRecord.id, - ...modelFieldsToSave, + promises.push( + API.graphql({ + query: updateMovie.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + movieKey: movieRecord.movieKey, + title: movieRecord.title, + genre: movieRecord.genre, + ...modelFieldsToSave, + }, }, - }, - }); + }) + ); + await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -7922,323 +10849,196 @@ export default function MyPostForm(props) { } } }} - {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...getOverrideProps(overrides, \\"MovieUpdateForm\\")} {...rest} > - <Flex - justifyContent=\\"space-between\\" - {...getOverrideProps(overrides, \\"CTAFlex\\")} - > - <Button - children=\\"Reset\\" - type=\\"reset\\" - onClick={(event) => { - event.preventDefault(); - resetStateValues(); - }} - isDisabled={!(idProp || postModelProp)} - {...getOverrideProps(overrides, \\"ResetButton\\")} - ></Button> - <Flex - gap=\\"15px\\" - {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} - > - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> - <Button - children=\\"Submit\\" - type=\\"submit\\" - variation=\\"primary\\" - isDisabled={ - !(idProp || postModelProp) || - Object.values(errors).some((e) => e?.hasError) - } - {...getOverrideProps(overrides, \\"SubmitButton\\")} - ></Button> - </Flex> - </Flex> - <TextAreaField - label=\\"Label\\" - value={TextAreaFieldbbd63464} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464: value, - caption, - username, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.TextAreaFieldbbd63464 ?? value; - } - if (errors.TextAreaFieldbbd63464?.hasError) { - runValidationTasks(\\"TextAreaFieldbbd63464\\", value); - } - setTextAreaFieldbbd63464(value); - }} - onBlur={() => - runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) - } - errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} - hasError={errors.TextAreaFieldbbd63464?.hasError} - {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} - ></TextAreaField> - <TextField - label=\\"Caption\\" - isRequired={false} - isReadOnly={false} - value={caption} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption: value, - username, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.caption ?? value; - } - if (errors.caption?.hasError) { - runValidationTasks(\\"caption\\", value); - } - setCaption(value); - }} - onBlur={() => runValidationTasks(\\"caption\\", caption)} - errorMessage={errors.caption?.errorMessage} - hasError={errors.caption?.hasError} - {...getOverrideProps(overrides, \\"caption\\")} - ></TextField> - <TextField - label=\\"Username\\" - isRequired={false} - isReadOnly={false} - value={username} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption, - username: value, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.username ?? value; - } - if (errors.username?.hasError) { - runValidationTasks(\\"username\\", value); - } - setUsername(value); - }} - onBlur={() => runValidationTasks(\\"username\\", username)} - errorMessage={errors.username?.errorMessage} - hasError={errors.username?.hasError} - {...getOverrideProps(overrides, \\"username\\")} - ></TextField> <TextField - label=\\"Profile url\\" - isRequired={false} - isReadOnly={false} - value={profile_url} + label=\\"Movie key\\" + isRequired={true} + isReadOnly={true} + value={movieKey} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url: value, - post_url, - metadata, - nonModelField, - nonModelFieldArray, + movieKey: value, + title, + genre, + rating, + tags, }; const result = onChange(modelFields); - value = result?.profile_url ?? value; + value = result?.movieKey ?? value; } - if (errors.profile_url?.hasError) { - runValidationTasks(\\"profile_url\\", value); + if (errors.movieKey?.hasError) { + runValidationTasks(\\"movieKey\\", value); } - setProfile_url(value); + setMovieKey(value); }} - onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} - errorMessage={errors.profile_url?.errorMessage} - hasError={errors.profile_url?.hasError} - {...getOverrideProps(overrides, \\"profile_url\\")} + onBlur={() => runValidationTasks(\\"movieKey\\", movieKey)} + errorMessage={errors.movieKey?.errorMessage} + hasError={errors.movieKey?.hasError} + {...getOverrideProps(overrides, \\"movieKey\\")} ></TextField> <TextField - label=\\"Post url\\" - isRequired={false} - isReadOnly={false} - value={post_url} + label=\\"Title\\" + isRequired={true} + isReadOnly={true} + value={title} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url: value, - metadata, - nonModelField, - nonModelFieldArray, + movieKey, + title: value, + genre, + rating, + tags, }; const result = onChange(modelFields); - value = result?.post_url ?? value; + value = result?.title ?? value; } - if (errors.post_url?.hasError) { - runValidationTasks(\\"post_url\\", value); + if (errors.title?.hasError) { + runValidationTasks(\\"title\\", value); } - setPost_url(value); + setTitle(value); }} - onBlur={() => runValidationTasks(\\"post_url\\", post_url)} - errorMessage={errors.post_url?.errorMessage} - hasError={errors.post_url?.hasError} - {...getOverrideProps(overrides, \\"post_url\\")} + onBlur={() => runValidationTasks(\\"title\\", title)} + errorMessage={errors.title?.errorMessage} + hasError={errors.title?.hasError} + {...getOverrideProps(overrides, \\"title\\")} ></TextField> - <TextAreaField - label=\\"Metadata\\" - isRequired={false} - isReadOnly={false} - value={metadata} + <TextField + label=\\"Genre\\" + isRequired={true} + isReadOnly={true} + value={genre} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata: value, - nonModelField, - nonModelFieldArray, + movieKey, + title, + genre: value, + rating, + tags, }; const result = onChange(modelFields); - value = result?.metadata ?? value; + value = result?.genre ?? value; } - if (errors.metadata?.hasError) { - runValidationTasks(\\"metadata\\", value); + if (errors.genre?.hasError) { + runValidationTasks(\\"genre\\", value); } - setMetadata(value); + setGenre(value); }} - onBlur={() => runValidationTasks(\\"metadata\\", metadata)} - errorMessage={errors.metadata?.errorMessage} - hasError={errors.metadata?.hasError} - {...getOverrideProps(overrides, \\"metadata\\")} - ></TextAreaField> - <TextAreaField - label=\\"Non model field\\" + onBlur={() => runValidationTasks(\\"genre\\", genre)} + errorMessage={errors.genre?.errorMessage} + hasError={errors.genre?.hasError} + {...getOverrideProps(overrides, \\"genre\\")} + ></TextField> + <TextField + label=\\"Rating\\" isRequired={false} isReadOnly={false} - value={nonModelField} + value={rating} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata, - nonModelField: value, - nonModelFieldArray, + movieKey, + title, + genre, + rating: value, + tags, }; const result = onChange(modelFields); - value = result?.nonModelField ?? value; + value = result?.rating ?? value; } - if (errors.nonModelField?.hasError) { - runValidationTasks(\\"nonModelField\\", value); + if (errors.rating?.hasError) { + runValidationTasks(\\"rating\\", value); } - setNonModelField(value); + setRating(value); }} - onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} - errorMessage={errors.nonModelField?.errorMessage} - hasError={errors.nonModelField?.hasError} - {...getOverrideProps(overrides, \\"nonModelField\\")} - ></TextAreaField> + onBlur={() => runValidationTasks(\\"rating\\", rating)} + errorMessage={errors.rating?.errorMessage} + hasError={errors.rating?.hasError} + {...getOverrideProps(overrides, \\"rating\\")} + ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray: values, + movieKey, + title, + genre, + rating, + tags: values, }; const result = onChange(modelFields); - values = result?.nonModelFieldArray ?? values; + values = result?.tags ?? values; } - setNonModelFieldArray(values); - setCurrentNonModelFieldArrayValue(\\"\\"); + setTags(values); + setCurrentTagsValue(undefined); + setCurrentTagsDisplayValue(\\"\\"); }} - currentFieldValue={currentNonModelFieldArrayValue} - label={\\"Non model field array\\"} - items={nonModelFieldArray} - hasError={errors?.nonModelFieldArray?.hasError} + currentFieldValue={currentTagsValue} + label={\\"Tags\\"} + items={tags} + hasError={errors?.tags?.hasError} runValidationTasks={async () => - await runValidationTasks( - \\"nonModelFieldArray\\", - currentNonModelFieldArrayValue - ) + await runValidationTasks(\\"tags\\", currentTagsValue) } - errorMessage={errors?.nonModelFieldArray?.errorMessage} - setFieldValue={setCurrentNonModelFieldArrayValue} - inputFieldRef={nonModelFieldArrayRef} + errorMessage={errors?.tags?.errorMessage} + getBadgeText={getDisplayValue.tags} + setFieldValue={(model) => { + setCurrentTagsDisplayValue(model ? getDisplayValue.tags(model) : \\"\\"); + setCurrentTagsValue(model); + }} + inputFieldRef={tagsRef} defaultFieldValue={\\"\\"} > - <TextAreaField - label=\\"Non model field array\\" + <Autocomplete + label=\\"Tags\\" isRequired={false} isReadOnly={false} - value={currentNonModelFieldArrayValue} + placeholder=\\"Search Tag\\" + value={currentTagsDisplayValue} + options={tagsRecords.map((r) => ({ + id: getIDValue.tags?.(r), + label: getDisplayValue.tags?.(r), + }))} + isLoading={tagsLoading} + onSelect={({ id, label }) => { + setCurrentTagsValue( + tagsRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentTagsDisplayValue(label); + runValidationTasks(\\"tags\\", label); + }} + onClear={() => { + setCurrentTagsDisplayValue(\\"\\"); + }} onChange={(e) => { let { value } = e.target; - if (errors.nonModelFieldArray?.hasError) { - runValidationTasks(\\"nonModelFieldArray\\", value); + fetchTagsRecords(value); + if (errors.tags?.hasError) { + runValidationTasks(\\"tags\\", value); } - setCurrentNonModelFieldArrayValue(value); + setCurrentTagsDisplayValue(value); + setCurrentTagsValue(undefined); }} - onBlur={() => - runValidationTasks( - \\"nonModelFieldArray\\", - currentNonModelFieldArrayValue - ) - } - errorMessage={errors.nonModelFieldArray?.errorMessage} - hasError={errors.nonModelFieldArray?.hasError} - ref={nonModelFieldArrayRef} + onBlur={() => runValidationTasks(\\"tags\\", currentTagsDisplayValue)} + errorMessage={errors.tags?.errorMessage} + hasError={errors.tags?.hasError} + ref={tagsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} - ></TextAreaField> + {...getOverrideProps(overrides, \\"tags\\")} + ></Autocomplete> </ArrayField> <Flex justifyContent=\\"space-between\\" @@ -8251,27 +11051,19 @@ export default function MyPostForm(props) { event.preventDefault(); resetStateValues(); }} - isDisabled={!(idProp || postModelProp)} + isDisabled={!(idProp || movieModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ - !(idProp || postModelProp) || + !(idProp || movieModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} @@ -8284,65 +11076,59 @@ export default function MyPostForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with composite primary key 2`] = ` "import * as React from \\"react\\"; -import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Post } from \\"../API\\"; +import { Movie, Tag } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; -export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type MyPostFormInputValues = { - TextAreaFieldbbd63464?: string; - caption?: string; - username?: string; - profile_url?: string; - post_url?: string; - metadata?: string; - nonModelField?: string; - nonModelFieldArray?: string[]; -}; -export declare type MyPostFormValidationValues = { - TextAreaFieldbbd63464?: ValidationFunction<string>; - caption?: ValidationFunction<string>; - username?: ValidationFunction<string>; - profile_url?: ValidationFunction<string>; - post_url?: ValidationFunction<string>; - metadata?: ValidationFunction<string>; - nonModelField?: ValidationFunction<string>; - nonModelFieldArray?: ValidationFunction<string>; +export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; +export declare type MovieUpdateFormInputValues = { + movieKey?: string; + title?: string; + genre?: string; + rating?: string; + tags?: Tag[]; +}; +export declare type MovieUpdateFormValidationValues = { + movieKey?: ValidationFunction<string>; + title?: ValidationFunction<string>; + genre?: ValidationFunction<string>; + rating?: ValidationFunction<string>; + tags?: ValidationFunction<Tag>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type MyPostFormOverridesProps = { - MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; - TextAreaFieldbbd63464?: PrimitiveOverrideProps<TextAreaFieldProps>; - caption?: PrimitiveOverrideProps<TextFieldProps>; - username?: PrimitiveOverrideProps<TextFieldProps>; - profile_url?: PrimitiveOverrideProps<TextFieldProps>; - post_url?: PrimitiveOverrideProps<TextFieldProps>; - metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; - nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; - nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; +export declare type MovieUpdateFormOverridesProps = { + MovieUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; + movieKey?: PrimitiveOverrideProps<TextFieldProps>; + title?: PrimitiveOverrideProps<TextFieldProps>; + genre?: PrimitiveOverrideProps<TextFieldProps>; + rating?: PrimitiveOverrideProps<TextFieldProps>; + tags?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type MyPostFormProps = React.PropsWithChildren<{ - overrides?: MyPostFormOverridesProps | undefined | null; +export declare type MovieUpdateFormProps = React.PropsWithChildren<{ + overrides?: MovieUpdateFormOverridesProps | undefined | null; } & { - id?: string; - post?: Post; - onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onSuccess?: (fields: MyPostFormInputValues) => void; - onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onValidate?: MyPostFormValidationValues; + id?: { + movieKey: string; + title: string; + genre: string; + }; + movie?: Movie; + onSubmit?: (fields: MovieUpdateFormInputValues) => MovieUpdateFormInputValues; + onSuccess?: (fields: MovieUpdateFormInputValues) => void; + onError?: (fields: MovieUpdateFormInputValues, errorMessage: string) => void; + onChange?: (fields: MovieUpdateFormInputValues) => MovieUpdateFormInputValues; + onValidate?: MovieUpdateFormValidationValues; } & React.CSSProperties>; -export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +export default function MovieUpdateForm(props: MovieUpdateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate an update form with composite primary key 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -8360,18 +11146,9 @@ import { } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { - getMovie, - listMovieTags, - listTags, - movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre, -} from \\"../graphql/queries\\"; import { API } from \\"aws-amplify\\"; -import { - createMovieTags, - deleteMovieTags, - updateMovie, -} from \\"../graphql/mutations\\"; +import { getComment, getPost, listPosts } from \\"../graphql/queries\\"; +import { updateComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -8527,10 +11304,10 @@ function ArrayField({ </React.Fragment> ); } -export default function MovieUpdateForm(props) { +export default function CommentUpdateForm(props) { const { id: idProp, - movie: movieModelProp, + comment: commentModelProp, onSuccess, onError, onSubmit, @@ -8540,92 +11317,90 @@ export default function MovieUpdateForm(props) { ...rest } = props; const initialValues = { - movieKey: \\"\\", - title: \\"\\", - genre: \\"\\", - rating: \\"\\", - tags: [], + content: \\"\\", + postID: undefined, + Post: undefined, + post: \\"\\", }; - const [movieKey, setMovieKey] = React.useState(initialValues.movieKey); - const [title, setTitle] = React.useState(initialValues.title); - const [genre, setGenre] = React.useState(initialValues.genre); - const [rating, setRating] = React.useState(initialValues.rating); - const [tags, setTags] = React.useState(initialValues.tags); - const [tagsLoading, setTagsLoading] = React.useState(false); - const [tagsRecords, setTagsRecords] = React.useState([]); + const [content, setContent] = React.useState(initialValues.content); + const [postID, setPostID] = React.useState(initialValues.postID); + const [postIDLoading, setPostIDLoading] = React.useState(false); + const [postIDRecords, setPostIDRecords] = React.useState([]); + const [selectedPostIDRecords, setSelectedPostIDRecords] = React.useState([]); + const [Post, setPost] = React.useState(initialValues.Post); + const [PostLoading, setPostLoading] = React.useState(false); + const [PostRecords, setPostRecords] = React.useState([]); + const [post1, setPost1] = React.useState(initialValues.post); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - const cleanValues = movieRecord - ? { ...initialValues, ...movieRecord, tags: linkedTags } + const cleanValues = commentRecord + ? { ...initialValues, ...commentRecord, postID, Post } : initialValues; - setMovieKey(cleanValues.movieKey); - setTitle(cleanValues.title); - setGenre(cleanValues.genre); - setRating(cleanValues.rating); - setTags(cleanValues.tags ?? []); - setCurrentTagsValue(undefined); - setCurrentTagsDisplayValue(\\"\\"); + setContent(cleanValues.content); + setPostID(cleanValues.postID); + setCurrentPostIDValue(undefined); + setCurrentPostIDDisplayValue(\\"\\"); + setPost(cleanValues.Post); + setCurrentPostValue(undefined); + setCurrentPostDisplayValue(\\"\\"); + setPost1(cleanValues.post); setErrors({}); }; - const [movieRecord, setMovieRecord] = React.useState(movieModelProp); - const [linkedTags, setLinkedTags] = React.useState([]); - const canUnlinkTags = false; + const [commentRecord, setCommentRecord] = React.useState(commentModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ - query: getMovie.replaceAll(\\"__typename\\", \\"\\"), - variables: { ...idProp }, + query: getComment.replaceAll(\\"__typename\\", \\"\\"), + variables: { id: idProp }, }) - )?.data?.getMovie - : movieModelProp; - const linkedTags = record + )?.data?.getComment + : commentModelProp; + const postIDRecord = record ? record.postID : undefined; + const postRecord = postIDRecord ? ( await API.graphql({ - query: - movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre.replaceAll( - \\"__typename\\", - \\"\\" - ), - variables: { - movieMovieKey: record.movieKey, - movietitle: record.title, - moviegenre: record.genre, - }, + query: getPost.replaceAll(\\"__typename\\", \\"\\"), + variables: { id: postIDRecord }, }) - ).data.movieTagsByMovieMovieKeyAndMovietitleAndMoviegenre.items.map( - (t) => t.tag - ) - : []; - setLinkedTags(linkedTags); - setMovieRecord(record); + )?.data?.getPost + : undefined; + setPostID(postIDRecord); + setSelectedPostIDRecords([postRecord]); + const PostRecord = record ? await record.Post : undefined; + setPost(PostRecord); + setCommentRecord(record); }; queryData(); - }, [idProp, movieModelProp]); - React.useEffect(resetStateValues, [movieRecord, linkedTags]); - const [currentTagsDisplayValue, setCurrentTagsDisplayValue] = + }, [idProp, commentModelProp]); + React.useEffect(resetStateValues, [commentRecord, postID, Post]); + const [currentPostIDDisplayValue, setCurrentPostIDDisplayValue] = React.useState(\\"\\"); - const [currentTagsValue, setCurrentTagsValue] = React.useState(undefined); - const tagsRef = React.createRef(); + const [currentPostIDValue, setCurrentPostIDValue] = React.useState(undefined); + const postIDRef = React.createRef(); + const [currentPostDisplayValue, setCurrentPostDisplayValue] = + React.useState(\\"\\"); + const [currentPostValue, setCurrentPostValue] = React.useState(undefined); + const PostRef = React.createRef(); const getIDValue = { - tags: (r) => JSON.stringify({ id: r?.id }), + Post: (r) => JSON.stringify({ id: r?.id }), }; - const tagsIdSet = new Set( - Array.isArray(tags) - ? tags.map((r) => getIDValue.tags?.(r)) - : getIDValue.tags?.(tags) + const PostIdSet = new Set( + Array.isArray(Post) + ? Post.map((r) => getIDValue.Post?.(r)) + : getIDValue.Post?.(Post) ); const getDisplayValue = { - tags: (r) => \`\${r?.label ? r?.label + \\" - \\" : \\"\\"}\${r?.id}\`, + postID: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, + Post: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { - movieKey: [{ type: \\"Required\\" }], - title: [{ type: \\"Required\\" }], - genre: [{ type: \\"Required\\" }], - rating: [], - tags: [], + content: [], + postID: [{ type: \\"Required\\" }], + Post: [], + post: [], }; const runValidationTasks = async ( fieldName, @@ -8644,15 +11419,15 @@ export default function MovieUpdateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchTagsRecords = async (value) => { - setTagsLoading(true); + const fetchPostIDRecords = async (value) => { + setPostIDLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, filter: { - or: [{ label: { contains: value } }, { id: { contains: value } }], + or: [{ title: { contains: value } }, { id: { contains: value } }], }, }; if (newNext) { @@ -8660,21 +11435,49 @@ export default function MovieUpdateForm(props) { } const result = ( await API.graphql({ - query: listTags.replaceAll(\\"__typename\\", \\"\\"), + query: listPosts.replaceAll(\\"__typename\\", \\"\\"), variables, }) - )?.data?.listTags?.items; + )?.data?.listPosts?.items; + var loaded = result.filter((item) => postID !== item.id); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setPostIDRecords(newOptions.slice(0, autocompleteLength)); + setPostIDLoading(false); + }; + const fetchPostRecords = async (value) => { + setPostLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ title: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listPosts.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + )?.data?.listPosts?.items; var loaded = result.filter( - (item) => !tagsIdSet.has(getIDValue.tags?.(item)) + (item) => !PostIdSet.has(getIDValue.Post?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setTagsRecords(newOptions.slice(0, autocompleteLength)); - setTagsLoading(false); + setPostRecords(newOptions.slice(0, autocompleteLength)); + setPostLoading(false); }; React.useEffect(() => { - fetchTagsRecords(\\"\\"); + fetchPostIDRecords(\\"\\"); + fetchPostRecords(\\"\\"); }, []); return ( <Grid @@ -8684,12 +11487,11 @@ export default function MovieUpdateForm(props) { padding=\\"20px\\" onSubmit={async (event) => { event.preventDefault(); - let modelFields = { - movieKey, - title, - genre, - rating: rating ?? null, - tags: tags ?? null, + let modelFields = { + content: content ?? null, + postID, + Post: Post ?? null, + post: post ?? null, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -8727,115 +11529,20 @@ export default function MovieUpdateForm(props) { modelFields[key] = null; } }); - const promises = []; - const tagsToLinkMap = new Map(); - const tagsToUnLinkMap = new Map(); - const tagsMap = new Map(); - const linkedTagsMap = new Map(); - tags.forEach((r) => { - const count = tagsMap.get(getIDValue.tags?.(r)); - const newCount = count ? count + 1 : 1; - tagsMap.set(getIDValue.tags?.(r), newCount); - }); - linkedTags.forEach((r) => { - const count = linkedTagsMap.get(getIDValue.tags?.(r)); - const newCount = count ? count + 1 : 1; - linkedTagsMap.set(getIDValue.tags?.(r), newCount); - }); - linkedTagsMap.forEach((count, id) => { - const newCount = tagsMap.get(id); - if (newCount) { - const diffCount = count - newCount; - if (diffCount > 0) { - tagsToUnLinkMap.set(id, diffCount); - } - } else { - tagsToUnLinkMap.set(id, count); - } - }); - tagsMap.forEach((count, id) => { - const originalCount = linkedTagsMap.get(id); - if (originalCount) { - const diffCount = count - originalCount; - if (diffCount > 0) { - tagsToLinkMap.set(id, diffCount); - } - } else { - tagsToLinkMap.set(id, count); - } - }); - tagsToUnLinkMap.forEach(async (count, id) => { - const recordKeys = JSON.parse(id); - const movieTagsRecords = ( - await API.graphql({ - query: listMovieTags.replaceAll(\\"__typename\\", \\"\\"), - variables: { - filter: { - and: [ - { tagId: { eq: recordKeys.id } }, - { movieMovieKey: { eq: movieRecord.movieKey } }, - { movietitle: { eq: movieRecord.title } }, - { moviegenre: { eq: movieRecord.genre } }, - ], - }, - }, - }) - )?.data?.listMovieTags?.items; - for (let i = 0; i < count; i++) { - promises.push( - API.graphql({ - query: deleteMovieTags.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - id: movieTagsRecords[i].id, - }, - }, - }) - ); - } - }); - tagsToLinkMap.forEach((count, id) => { - const tagToLink = tagRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ); - for (let i = count; i > 0; i--) { - promises.push( - API.graphql({ - query: createMovieTags.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - movieMovieKey: movieRecord.movieKey, - movietitle: movieRecord.title, - moviegenre: movieRecord.genre, - tagId: tagToLink.id, - }, - }, - }) - ); - } - }); const modelFieldsToSave = { - movieKey: modelFields.movieKey, - title: modelFields.title, - genre: modelFields.genre, - rating: modelFields.rating ?? null, + content: modelFields.content ?? null, + postID: modelFields.postID, + postCommentsId: modelFields?.Post?.id ?? null, }; - promises.push( - API.graphql({ - query: updateMovie.replaceAll(\\"__typename\\", \\"\\"), - variables: { - input: { - movieKey: movieRecord.movieKey, - title: movieRecord.title, - genre: movieRecord.genre, - ...modelFieldsToSave, - }, + await API.graphql({ + query: updateComment.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + id: commentRecord.id, + ...modelFieldsToSave, }, - }) - ); - await Promise.all(promises); + }, + }); if (onSuccess) { onSuccess(modelFields); } @@ -8846,197 +11553,233 @@ export default function MovieUpdateForm(props) { } } }} - {...getOverrideProps(overrides, \\"MovieUpdateForm\\")} + {...getOverrideProps(overrides, \\"CommentUpdateForm\\")} {...rest} > <TextField - label=\\"Movie key\\" - isRequired={true} - isReadOnly={true} - value={movieKey} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - movieKey: value, - title, - genre, - rating, - tags, - }; - const result = onChange(modelFields); - value = result?.movieKey ?? value; - } - if (errors.movieKey?.hasError) { - runValidationTasks(\\"movieKey\\", value); - } - setMovieKey(value); - }} - onBlur={() => runValidationTasks(\\"movieKey\\", movieKey)} - errorMessage={errors.movieKey?.errorMessage} - hasError={errors.movieKey?.hasError} - {...getOverrideProps(overrides, \\"movieKey\\")} - ></TextField> - <TextField - label=\\"Title\\" - isRequired={true} - isReadOnly={true} - value={title} + label=\\"Content\\" + isRequired={false} + isReadOnly={false} + value={content} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - movieKey, - title: value, - genre, - rating, - tags, + content: value, + postID, + Post, + post: post1, }; const result = onChange(modelFields); - value = result?.title ?? value; + value = result?.content ?? value; } - if (errors.title?.hasError) { - runValidationTasks(\\"title\\", value); + if (errors.content?.hasError) { + runValidationTasks(\\"content\\", value); } - setTitle(value); + setContent(value); }} - onBlur={() => runValidationTasks(\\"title\\", title)} - errorMessage={errors.title?.errorMessage} - hasError={errors.title?.hasError} - {...getOverrideProps(overrides, \\"title\\")} + onBlur={() => runValidationTasks(\\"content\\", content)} + errorMessage={errors.content?.errorMessage} + hasError={errors.content?.hasError} + {...getOverrideProps(overrides, \\"content\\")} ></TextField> - <TextField - label=\\"Genre\\" - isRequired={true} - isReadOnly={true} - value={genre} - onChange={(e) => { - let { value } = e.target; + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; if (onChange) { const modelFields = { - movieKey, - title, - genre: value, - rating, - tags, + content, + postID: value, + Post, + post: post1, }; const result = onChange(modelFields); - value = result?.genre ?? value; - } - if (errors.genre?.hasError) { - runValidationTasks(\\"genre\\", value); + value = result?.postID ?? value; } - setGenre(value); + setPostID(value); + setCurrentPostIDValue(undefined); }} - onBlur={() => runValidationTasks(\\"genre\\", genre)} - errorMessage={errors.genre?.errorMessage} - hasError={errors.genre?.hasError} - {...getOverrideProps(overrides, \\"genre\\")} - ></TextField> - <TextField - label=\\"Rating\\" - isRequired={false} - isReadOnly={false} - value={rating} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - movieKey, - title, - genre, - rating: value, - tags, - }; - const result = onChange(modelFields); - value = result?.rating ?? value; - } - if (errors.rating?.hasError) { - runValidationTasks(\\"rating\\", value); + currentFieldValue={currentPostIDValue} + label={\\"Post id\\"} + items={postID ? [postID] : []} + hasError={errors?.postID?.hasError} + runValidationTasks={async () => + await runValidationTasks(\\"postID\\", currentPostIDValue) + } + errorMessage={errors?.postID?.errorMessage} + getBadgeText={(value) => + value + ? getDisplayValue.postID( + postIDRecords.find((r) => r.id === value) ?? + selectedPostIDRecords.find((r) => r.id === value) + ) + : \\"\\" + } + setFieldValue={(value) => { + setCurrentPostIDDisplayValue( + value + ? getDisplayValue.postID( + postIDRecords.find((r) => r.id === value) ?? + selectedPostIDRecords.find((r) => r.id === value) + ) + : \\"\\" + ); + setCurrentPostIDValue(value); + const selectedRecord = postIDRecords.find((r) => r.id === value); + if (selectedRecord) { + setSelectedPostIDRecords([selectedRecord]); } - setRating(value); }} - onBlur={() => runValidationTasks(\\"rating\\", rating)} - errorMessage={errors.rating?.errorMessage} - hasError={errors.rating?.hasError} - {...getOverrideProps(overrides, \\"rating\\")} - ></TextField> + inputFieldRef={postIDRef} + defaultFieldValue={\\"\\"} + > + <Autocomplete + label=\\"Post id\\" + isRequired={true} + isReadOnly={false} + placeholder=\\"Search Post\\" + value={currentPostIDDisplayValue} + options={postIDRecords + .filter( + (r, i, arr) => + arr.findIndex((member) => member?.id === r?.id) === i + ) + .map((r) => ({ + id: r?.id, + label: getDisplayValue.postID?.(r), + }))} + isLoading={postIDLoading} + onSelect={({ id, label }) => { + setCurrentPostIDValue(id); + setCurrentPostIDDisplayValue(label); + runValidationTasks(\\"postID\\", label); + }} + onClear={() => { + setCurrentPostIDDisplayValue(\\"\\"); + }} + defaultValue={postID} + onChange={(e) => { + let { value } = e.target; + fetchPostIDRecords(value); + if (errors.postID?.hasError) { + runValidationTasks(\\"postID\\", value); + } + setCurrentPostIDDisplayValue(value); + setCurrentPostIDValue(undefined); + }} + onBlur={() => runValidationTasks(\\"postID\\", currentPostIDValue)} + errorMessage={errors.postID?.errorMessage} + hasError={errors.postID?.hasError} + ref={postIDRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"postID\\")} + ></Autocomplete> + </ArrayField> <ArrayField + lengthLimit={1} onChange={async (items) => { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { - movieKey, - title, - genre, - rating, - tags: values, + content, + postID, + Post: value, + post: post1, }; const result = onChange(modelFields); - values = result?.tags ?? values; + value = result?.Post ?? value; } - setTags(values); - setCurrentTagsValue(undefined); - setCurrentTagsDisplayValue(\\"\\"); + setPost(value); + setCurrentPostValue(undefined); + setCurrentPostDisplayValue(\\"\\"); }} - currentFieldValue={currentTagsValue} - label={\\"Tags\\"} - items={tags} - hasError={errors?.tags?.hasError} + currentFieldValue={currentPostValue} + label={\\"Post\\"} + items={Post ? [Post] : []} + hasError={errors?.Post?.hasError} runValidationTasks={async () => - await runValidationTasks(\\"tags\\", currentTagsValue) + await runValidationTasks(\\"Post\\", currentPostValue) } - errorMessage={errors?.tags?.errorMessage} - getBadgeText={getDisplayValue.tags} + errorMessage={errors?.Post?.errorMessage} + getBadgeText={getDisplayValue.Post} setFieldValue={(model) => { - setCurrentTagsDisplayValue(model ? getDisplayValue.tags(model) : \\"\\"); - setCurrentTagsValue(model); + setCurrentPostDisplayValue(model ? getDisplayValue.Post(model) : \\"\\"); + setCurrentPostValue(model); }} - inputFieldRef={tagsRef} + inputFieldRef={PostRef} defaultFieldValue={\\"\\"} > <Autocomplete - label=\\"Tags\\" + label=\\"Post\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search Tag\\" - value={currentTagsDisplayValue} - options={tagsRecords.map((r) => ({ - id: getIDValue.tags?.(r), - label: getDisplayValue.tags?.(r), + placeholder=\\"Search Post\\" + value={currentPostDisplayValue} + options={PostRecords.filter( + (r) => !PostIdSet.has(getIDValue.Post?.(r)) + ).map((r) => ({ + id: getIDValue.Post?.(r), + label: getDisplayValue.Post?.(r), }))} - isLoading={tagsLoading} + isLoading={PostLoading} onSelect={({ id, label }) => { - setCurrentTagsValue( - tagsRecords.find((r) => + setCurrentPostValue( + PostRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentTagsDisplayValue(label); - runValidationTasks(\\"tags\\", label); + setCurrentPostDisplayValue(label); + runValidationTasks(\\"Post\\", label); }} onClear={() => { - setCurrentTagsDisplayValue(\\"\\"); + setCurrentPostDisplayValue(\\"\\"); }} + defaultValue={Post} onChange={(e) => { let { value } = e.target; - fetchTagsRecords(value); - if (errors.tags?.hasError) { - runValidationTasks(\\"tags\\", value); + fetchPostRecords(value); + if (errors.Post?.hasError) { + runValidationTasks(\\"Post\\", value); } - setCurrentTagsDisplayValue(value); - setCurrentTagsValue(undefined); + setCurrentPostDisplayValue(value); + setCurrentPostValue(undefined); }} - onBlur={() => runValidationTasks(\\"tags\\", currentTagsDisplayValue)} - errorMessage={errors.tags?.errorMessage} - hasError={errors.tags?.hasError} - ref={tagsRef} + onBlur={() => runValidationTasks(\\"Post\\", currentPostDisplayValue)} + errorMessage={errors.Post?.errorMessage} + hasError={errors.Post?.hasError} + ref={PostRef} labelHidden={true} - {...getOverrideProps(overrides, \\"tags\\")} + {...getOverrideProps(overrides, \\"Post\\")} ></Autocomplete> </ArrayField> + <TextField + label=\\"Label\\" + value={post1} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + content, + postID, + Post, + post: value, + }; + const result = onChange(modelFields); + value = result?.post ?? value; + } + if (errors.post?.hasError) { + runValidationTasks(\\"post\\", value); + } + setPost1(value); + }} + onBlur={() => runValidationTasks(\\"post\\", post1)} + errorMessage={errors.post?.errorMessage} + hasError={errors.post?.hasError} + {...getOverrideProps(overrides, \\"post\\")} + ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} @@ -9048,7 +11791,7 @@ export default function MovieUpdateForm(props) { event.preventDefault(); resetStateValues(); }} - isDisabled={!(idProp || movieModelProp)} + isDisabled={!(idProp || commentModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex @@ -9060,7 +11803,7 @@ export default function MovieUpdateForm(props) { type=\\"submit\\" variation=\\"primary\\" isDisabled={ - !(idProp || movieModelProp) || + !(idProp || commentModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} @@ -9073,59 +11816,52 @@ export default function MovieUpdateForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate an update form with composite primary key 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Movie, Tag } from \\"../API\\"; +import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type MovieUpdateFormInputValues = { - movieKey?: string; - title?: string; - genre?: string; - rating?: string; - tags?: Tag[]; +export declare type CommentUpdateFormInputValues = { + content?: string; + postID?: string; + Post?: Post; + post?: string; }; -export declare type MovieUpdateFormValidationValues = { - movieKey?: ValidationFunction<string>; - title?: ValidationFunction<string>; - genre?: ValidationFunction<string>; - rating?: ValidationFunction<string>; - tags?: ValidationFunction<Tag>; +export declare type CommentUpdateFormValidationValues = { + content?: ValidationFunction<string>; + postID?: ValidationFunction<string>; + Post?: ValidationFunction<Post>; + post?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type MovieUpdateFormOverridesProps = { - MovieUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; - movieKey?: PrimitiveOverrideProps<TextFieldProps>; - title?: PrimitiveOverrideProps<TextFieldProps>; - genre?: PrimitiveOverrideProps<TextFieldProps>; - rating?: PrimitiveOverrideProps<TextFieldProps>; - tags?: PrimitiveOverrideProps<AutocompleteProps>; +export declare type CommentUpdateFormOverridesProps = { + CommentUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; + content?: PrimitiveOverrideProps<TextFieldProps>; + postID?: PrimitiveOverrideProps<AutocompleteProps>; + Post?: PrimitiveOverrideProps<AutocompleteProps>; + post?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; -export declare type MovieUpdateFormProps = React.PropsWithChildren<{ - overrides?: MovieUpdateFormOverridesProps | undefined | null; +export declare type CommentUpdateFormProps = React.PropsWithChildren<{ + overrides?: CommentUpdateFormOverridesProps | undefined | null; } & { - id?: { - movieKey: string; - title: string; - genre: string; - }; - movie?: Movie; - onSubmit?: (fields: MovieUpdateFormInputValues) => MovieUpdateFormInputValues; - onSuccess?: (fields: MovieUpdateFormInputValues) => void; - onError?: (fields: MovieUpdateFormInputValues, errorMessage: string) => void; - onChange?: (fields: MovieUpdateFormInputValues) => MovieUpdateFormInputValues; - onValidate?: MovieUpdateFormValidationValues; + id?: string; + comment?: Comment; + onSubmit?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; + onSuccess?: (fields: CommentUpdateFormInputValues) => void; + onError?: (fields: CommentUpdateFormInputValues, errorMessage: string) => void; + onChange?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; + onValidate?: CommentUpdateFormValidationValues; } & React.CSSProperties>; -export default function MovieUpdateForm(props: MovieUpdateFormProps): React.ReactElement; +export default function CommentUpdateForm(props: CommentUpdateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship without types file - amplify js v6 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -9143,9 +11879,10 @@ import { } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { API } from \\"aws-amplify\\"; +import { generateClient } from \\"aws-amplify/api\\"; import { getComment, getPost, listPosts } from \\"../graphql/queries\\"; import { updateComment } from \\"../graphql/mutations\\"; +const client = generateClient(); function ArrayField({ items = [], onChange, @@ -9349,7 +12086,7 @@ export default function CommentUpdateForm(props) { const queryData = async () => { const record = idProp ? ( - await API.graphql({ + await client.graphql({ query: getComment.replaceAll(\\"__typename\\", \\"\\"), variables: { id: idProp }, }) @@ -9358,7 +12095,7 @@ export default function CommentUpdateForm(props) { const postIDRecord = record ? record.postID : undefined; const postRecord = postIDRecord ? ( - await API.graphql({ + await client.graphql({ query: getPost.replaceAll(\\"__typename\\", \\"\\"), variables: { id: postIDRecord }, }) @@ -9431,7 +12168,7 @@ export default function CommentUpdateForm(props) { variables[\\"nextToken\\"] = newNext; } const result = ( - await API.graphql({ + await client.graphql({ query: listPosts.replaceAll(\\"__typename\\", \\"\\"), variables, }) @@ -9458,7 +12195,7 @@ export default function CommentUpdateForm(props) { variables[\\"nextToken\\"] = newNext; } const result = ( - await API.graphql({ + await client.graphql({ query: listPosts.replaceAll(\\"__typename\\", \\"\\"), variables, }) @@ -9531,7 +12268,7 @@ export default function CommentUpdateForm(props) { postID: modelFields.postID, postCommentsId: modelFields?.Post?.id ?? null, }; - await API.graphql({ + await client.graphql({ query: updateComment.replaceAll(\\"__typename\\", \\"\\"), variables: { input: { @@ -9813,11 +12550,10 @@ export default function CommentUpdateForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship without types file - amplify js v6 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; @@ -9826,13 +12562,13 @@ export declare type ValidationFunction<T> = (value: T, validationResponse: Valid export declare type CommentUpdateFormInputValues = { content?: string; postID?: string; - Post?: Post; + Post?: any; post?: string; }; export declare type CommentUpdateFormValidationValues = { content?: ValidationFunction<string>; postID?: ValidationFunction<string>; - Post?: ValidationFunction<Post>; + Post?: ValidationFunction<any>; post?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; @@ -9847,7 +12583,7 @@ export declare type CommentUpdateFormProps = React.PropsWithChildren<{ overrides?: CommentUpdateFormOverridesProps | undefined | null; } & { id?: string; - comment?: Comment; + comment?: any; onSubmit?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onSuccess?: (fields: CommentUpdateFormInputValues) => void; onError?: (fields: CommentUpdateFormInputValues, errorMessage: string) => void; diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap index 2f2be4652..479bdbf4e 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap @@ -151,6 +151,59 @@ export default function UpdateCustomerButton( } `; +exports[`amplify render tests actions GraphQL DataStoreCreateItem - amplify js v6 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { generateClient } from \\"aws-amplify/api\\"; +import { createCustomer } from \\"../graphql/mutations\\"; +import { Customer } from \\"../API\\"; +import { + EscapeHatchProps, + getOverrideProps, +} from \\"@aws-amplify/ui-react/internal\\"; +import { Button, ButtonProps } from \\"@aws-amplify/ui-react\\"; + +export declare type PrimitiveOverrideProps<T> = Partial<T> & + React.DOMAttributes<HTMLDivElement>; +export declare type CreateCustomerButtonOverridesProps = { + CreateCustomerButton?: PrimitiveOverrideProps<ButtonProps>; +} & EscapeHatchProps; +export type CreateCustomerButtonProps = React.PropsWithChildren< + Partial<ButtonProps> & { + overrides?: CreateCustomerButtonOverridesProps | undefined | null; + } +>; +const client = generateClient(); +export default function CreateCustomerButton( + props: CreateCustomerButtonProps +): React.ReactElement { + const { overrides, ...rest } = props; + const createCustomerButtonOnClick = async () => { + await client.graphql({ + query: createCustomer.replaceAll(\\"__typename\\", \\"\\"), + variables: { + input: { + firstName: \\"Din\\", + lastName: \\"Djarin\\", + }, + }, + }); + }; + return ( + /* @ts-ignore: TS2322 */ + <Button + children=\\"Create\\" + onClick={() => { + createCustomerButtonOnClick(); + }} + {...getOverrideProps(overrides, \\"CreateCustomerButton\\")} + {...rest} + ></Button> + ); +} +" +`; + exports[`amplify render tests actions GraphQL DataStoreCreateItem 1`] = ` Object { "componentText": "/* eslint-disable */ @@ -1010,6 +1063,208 @@ export default function AuthorProfileCollection( " `; +exports[`amplify render tests collection GraphQL should render collection with data binding - amplify js v6 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { UserPreferences } from \\"../API\\"; +import { listUserPreferences, listUsers } from \\"../graphql/queries\\"; +import { + EscapeHatchProps, + getOverrideProps, + useDataStoreBinding, +} from \\"@aws-amplify/ui-react/internal\\"; +import { generateClient } from \\"aws-amplify/api\\"; +import { + Button, + ButtonProps, + Collection, + CollectionProps, + Flex, + FlexProps, + Pagination, + Placeholder, +} from \\"@aws-amplify/ui-react\\"; +import { MyFlexProps } from \\"./MyFlex\\"; + +export declare type PrimitiveOverrideProps<T> = Partial<T> & + React.DOMAttributes<HTMLDivElement>; +export declare type CollectionOfCustomButtonsOverridesProps = { + CollectionOfCustomButtons?: PrimitiveOverrideProps<CollectionProps>; + MyFlex?: PrimitiveOverrideProps<FlexProps>; + MyButton?: PrimitiveOverrideProps<ButtonProps>; +} & EscapeHatchProps; +export type CollectionOfCustomButtonsProps = React.PropsWithChildren< + Partial<CollectionProps<any>> & { + width?: Number; + backgroundColor?: String; + buttonColor?: UserPreferences; + buttonEnabled?: UserPreferences; + items?: any[]; + overrideItems?: (collectionItem: { + item: any; + index: number; + }) => MyFlexProps; + } & { + overrides?: CollectionOfCustomButtonsOverridesProps | undefined | null; + } +>; + +const nextToken = {}; +const apiCache = {}; +const client = generateClient(); +export default function CollectionOfCustomButtons( + props: CollectionOfCustomButtonsProps +): React.ReactElement { + const { + width, + backgroundColor, + buttonColor: buttonColorProp, + buttonEnabled: buttonEnabledProp, + items: itemsProp, + overrideItems, + overrides, + ...rest + } = props; + const [pageIndex, setPageIndex] = React.useState(1); + const [hasMorePages, setHasMorePages] = React.useState(true); + const [items, setItems] = React.useState([]); + const [isApiPagination, setIsApiPagination] = React.useState(false); + const [instanceKey, setInstanceKey] = React.useState(\\"newGuid\\"); + const [loading, setLoading] = React.useState(true); + const [maxViewed, setMaxViewed] = React.useState(1); + const pageSize = 6; + const isPaginated = true; + React.useEffect(() => { + nextToken[instanceKey] = \\"\\"; + apiCache[instanceKey] = []; + }, [instanceKey]); + React.useEffect(() => { + setIsApiPagination(!!!itemsProp); + }, [itemsProp]); + const handlePreviousPage = () => { + setPageIndex(pageIndex - 1); + }; + const handleNextPage = () => { + setPageIndex(pageIndex + 1); + }; + const jumpToPage = (pageNum?: number) => { + setPageIndex(pageNum!); + }; + const loadPage = async (page: number) => { + const cacheUntil = page * pageSize + 1; + const newCache = apiCache[instanceKey].slice(); + let newNext = nextToken[instanceKey]; + while ((newCache.length < cacheUntil || !isPaginated) && newNext != null) { + setLoading(true); + const variables: any = { + limit: pageSize, + filter: { + and: [ + { age: { gt: \\"10\\" } }, + { lastName: { beginsWith: \\"L\\" } }, + { + and: [ + { date: { ge: \\"2022-03-10\\" } }, + { date: { le: \\"2023-03-11\\" } }, + ], + }, + ], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await client.graphql({ + query: listUsers.replaceAll(\\"__typename\\", \\"\\"), + variables, + }) + ).data.listUsers; + newCache.push(...result.items); + newNext = result.nextToken; + } + const cacheSlice = isPaginated + ? newCache.slice((page - 1) * pageSize, page * pageSize) + : newCache; + setItems(cacheSlice); + setHasMorePages(!!newNext); + setLoading(false); + apiCache[instanceKey] = newCache; + nextToken[instanceKey] = newNext; + }; + React.useEffect(() => { + loadPage(pageIndex); + }, [pageIndex]); + React.useEffect(() => { + setMaxViewed(Math.max(maxViewed, pageIndex)); + }, [pageIndex, maxViewed, setMaxViewed]); + const buttonColorFilterObj = { userID: { eq: \\"user@email.com\\" } }; + const buttonColorDataStore = client.graphql({ + query: listUserPreferences.replaceAll(\\"__typename\\", \\"\\"), + variables: { ...buttonColorFilterObj }, + }).items[0]; + const buttonColor = + buttonColorProp !== undefined ? buttonColorProp : buttonColorDataStore; + const buttonEnabledFilterObj = { + and: [{ date: { ge: \\"2022-03-10\\" } }, { date: { le: \\"2023-03-11\\" } }], + }; + const buttonEnabledDataStore = client.graphql({ + query: listUserPreferences.replaceAll(\\"__typename\\", \\"\\"), + variables: { ...buttonEnabledFilterObj }, + }).items[0]; + const buttonEnabled = + buttonEnabledProp !== undefined + ? buttonEnabledProp + : buttonEnabledDataStore; + return ( + /* @ts-ignore: TS2322 */ + <div> + <Collection + type=\\"list\\" + gap=\\"1.5rem\\" + backgroundColor={backgroundColor} + itemsPerPage={pageSize} + isPaginated={!isApiPagination && isPaginated} + items={itemsProp || (loading ? new Array(pageSize).fill({}) : items)} + {...getOverrideProps(overrides, \\"CollectionOfCustomButtons\\")} + {...rest} + > + {(item, index) => { + if (loading) { + return <Placeholder key={index} size=\\"large\\" />; + } + return ( + <Flex + key={item.id} + {...(overrideItems && overrideItems({ item, index }))} + > + <Button + labelWidth={width} + backgroundColor={buttonColor?.favoriteColor} + disabled={isDisabled} + children={item.username || \\"hspain@gmail.com\\"} + {...(overrideItems && overrideItems({ item, index }))} + ></Button> + </Flex> + ); + }} + </Collection> + {isApiPagination && isPaginated && ( + <Pagination + currentPage={pageIndex} + totalPages={maxViewed} + hasMorePages={hasMorePages} + onNext={handleNextPage} + onPrevious={handlePreviousPage} + onChange={jumpToPage} + /> + )} + </div> + ); +} +" +`; + exports[`amplify render tests collection GraphQL should render collection with data binding 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts index 846abc987..adbce1695 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts @@ -733,6 +733,24 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should generate a create form - amplify js v6', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/post-datastore-create', + 'datastore/post', + { ...defaultCLIRenderConfig, ...rendererConfigWithGraphQL, dependencies: { 'aws-amplify': '^6.0.0' } }, + { isNonModelSupported: true, isRelationshipSupported: true }, + ); + + expect(componentText).not.toContain('import { API } from "aws-amplify";'); + expect(componentText).not.toContain(`await API.graphql`); + expect(componentText).toContain('import { generateClient } from "aws-amplify/api";'); + expect(componentText).toContain(`const client = generateClient();`); + expect(componentText).toContain(`await client.graphql`); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should generate a update form without relationships', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/post-datastore-update', @@ -757,6 +775,34 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should generate a update form without relationships - amplify js v6', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/post-datastore-update', + 'datastore/post', + { ...defaultCLIRenderConfig, ...rendererConfigWithGraphQL, dependencies: { 'aws-amplify': '^6.0.0' } }, + { isNonModelSupported: true, isRelationshipSupported: false }, + ); + + // check import for graphql operations + expect(componentText).not.toContain('import { API } from "aws-amplify";'); + expect(componentText).not.toContain(`await API.graphql`); + + expect(componentText).toContain('import { generateClient } from "aws-amplify/api";'); + expect(componentText).toContain(`const client = generateClient();`); + expect(componentText).toContain('import { getPost } from "../graphql/queries";'); + expect(componentText).toContain('import { updatePost } from "../graphql/mutations";'); + + // should not have DataStore.save call + expect(componentText).not.toContain('await DataStore.save('); + + // should call updatePost mutation onSubmit + expect(componentText).toContain(`await client.graphql`); + expect(componentText).toContain(`query: updatePost.replaceAll("__typename", ""),`); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should generate a create form with hasOne relationship', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/book-datastore-relationship', @@ -808,6 +854,28 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should generate an update form with hasMany relationship without types file - amplify js v6', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/relationships/update-comment', + 'datastore/relationships/has-many-comment', + { ...defaultCLIRenderConfig, ...noTypesFileConfig, dependencies: { 'aws-amplify': '^6.0.0' } }, + { isNonModelSupported: true, isRelationshipSupported: true }, + ); + + // check for import statement for graphql operation + expect(componentText).not.toContain('DataStore'); + expect(componentText).not.toContain('import { API } from "aws-amplify";'); + expect(componentText).not.toContain(`await API.graphql`); + + expect(componentText).toContain('import { generateClient } from "aws-amplify/api";'); + expect(componentText).toContain(`const client = generateClient();`); + expect(componentText).toContain('await client.graphql({'); + expect(componentText).toContain('query: updateComment'); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should generate a relationship update form with autocomplete', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/relationships/update-post', @@ -1123,6 +1191,25 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should 1:1 relationships without types file path - Create amplify js v6', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/owner-dog-create', + 'datastore/dog-owner-required', + { ...defaultCLIRenderConfig, ...noTypesFileConfig, dependencies: { 'aws-amplify': '^6.0.0' } }, + { isNonModelSupported: true, isRelationshipSupported: true }, + ); + + expect(componentText).not.toContain('import { API } from "aws-amplify";'); + expect(componentText).not.toContain(`await API.graphql`); + expect(componentText).not.toContain('cannot be unlinked because'); + expect(componentText).not.toContain('cannot be linked to '); + expect(componentText).toContain('import { generateClient } from "aws-amplify/api";'); + expect(componentText).toContain(`const client = generateClient();`); + expect(componentText).toContain('await client.graphql({'); + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should treat relationship as bidirectional without belongsTo', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/relationships/update-comment-no-belongsTo', diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts index 2941a1ac0..b938546e5 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts @@ -198,6 +198,19 @@ describe('amplify render tests', () => { expect(generatedCode.componentText).toMatchSnapshot(); }); + it('should render collection with data binding - amplify js v6', () => { + const { componentText } = generateWithAmplifyRenderer('collectionWithBinding', { + ...rendererConfigWithGraphQL, + dependencies: { 'aws-amplify': '^6.0.0' }, + }); + expect(componentText).not.toContain('import { API } from "aws-amplify";'); + expect(componentText).not.toContain(`await API.graphql`); + expect(componentText).toContain('import { generateClient } from "aws-amplify/api";'); + expect(componentText).toContain(`await client.graphql`); + + expect(componentText).toMatchSnapshot(); + }); + it('should render collection without data binding', () => { const generatedCode = generateWithAmplifyRenderer('collectionWithoutBinding', rendererConfigWithGraphQL); expect(generatedCode.componentText).toMatchSnapshot(); @@ -450,6 +463,18 @@ describe('amplify render tests', () => { ).toMatchSnapshot(); }); + it('DataStoreCreateItem - amplify js v6', () => { + const { componentText } = generateWithAmplifyRenderer('workflow/dataStoreCreateItem', { + ...rendererConfigWithGraphQL, + dependencies: { 'aws-amplify': '^6.0.0' }, + }); + expect(componentText).toMatchSnapshot(); + expect(componentText).not.toContain('import { API } from "aws-amplify";'); + expect(componentText).not.toContain(`await API.graphql`); + expect(componentText).toContain('import { generateClient } from "aws-amplify/api";'); + expect(componentText).toContain(`await client.graphql`); + }); + it('DataStoreUpdateItem', () => { expect( generateWithAmplifyRenderer('workflow/dataStoreUpdateItem', rendererConfigWithGraphQL), diff --git a/packages/codegen-ui-react/lib/amplify-ui-renderers/collection.ts b/packages/codegen-ui-react/lib/amplify-ui-renderers/collection.ts index 13bc442b7..7bfa83f43 100644 --- a/packages/codegen-ui-react/lib/amplify-ui-renderers/collection.ts +++ b/packages/codegen-ui-react/lib/amplify-ui-renderers/collection.ts @@ -36,6 +36,7 @@ import { buildOpeningElementProperties } from '../react-component-render-helper' import { ImportCollection, ImportValue } from '../imports'; import { DataApiKind, ReactRenderConfig } from '../react-render-config'; import { defaultRenderConfig } from '../react-studio-template-renderer-helper'; +import { getAmplifyJSAPIImport } from '../helpers/amplify-js-versioning'; export default class CollectionRenderer extends ReactComponentRenderer<BaseComponentProps> { constructor( @@ -57,7 +58,9 @@ export default class CollectionRenderer extends ReactComponentRenderer<BaseCompo this.importCollection.addImport('@aws-amplify/ui-react', this.component.componentType); if (this.renderConfig.apiConfiguration?.dataApi === 'GraphQL') { - this.importCollection.addMappedImport(ImportValue.API, ImportValue.PAGINATION, ImportValue.PLACEHOLDER); + const mappedImport = getAmplifyJSAPIImport(this.renderConfig.dependencies); + this.importCollection.addMappedImport(mappedImport); + this.importCollection.addMappedImport(ImportValue.PAGINATION, ImportValue.PLACEHOLDER); } return element; diff --git a/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts b/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts index db4a85d76..d45a4d60e 100644 --- a/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts +++ b/packages/codegen-ui-react/lib/amplify-ui-renderers/form.ts @@ -34,7 +34,7 @@ import { } from 'typescript'; import { ReactComponentRenderer } from '../react-component-renderer'; import { buildFormLayoutProperties, buildOpeningElementProperties } from '../react-component-render-helper'; -import { ImportCollection, ImportSource, ImportValue } from '../imports'; +import { ImportCollection, ImportSource } from '../imports'; import { buildExpression } from '../forms'; import { onSubmitValidationRun, buildModelFieldObject } from '../forms/form-renderer-helper'; import { hasTokenReference } from '../utils/forms/layout-helpers'; @@ -43,6 +43,7 @@ import { isModelDataType } from '../forms/form-renderer-helper/render-checkers'; import { replaceEmptyStringStatement } from '../forms/form-renderer-helper/cta-props'; import { ReactRenderConfig } from '../react-render-config'; import { defaultRenderConfig } from '../react-studio-template-renderer-helper'; +import { getAmplifyJSAPIImport } from '../helpers/amplify-js-versioning'; export default class FormRenderer extends ReactComponentRenderer<BaseComponentProps> { constructor( @@ -71,7 +72,8 @@ export default class FormRenderer extends ReactComponentRenderer<BaseComponentPr this.importCollection.addImport('@aws-amplify/ui-react', this.component.componentType); if (this.form.dataType.dataSourceType === 'DataStore' && this.isRenderingGraphQL()) { - this.importCollection.addMappedImport(ImportValue.API); + const mappedImport = getAmplifyJSAPIImport(); + this.importCollection.addMappedImport(mappedImport); } else if (this.form.dataType.dataSourceType === 'DataStore') { this.importCollection.addImport('aws-amplify', 'DataStore'); } @@ -166,6 +168,7 @@ export default class FormRenderer extends ReactComponentRenderer<BaseComponentPr dataSchemaMetadata, this.importCollection, 'apiConfiguration' in this.renderConfig ? this.renderConfig.apiConfiguration?.dataApi : undefined, + this.renderConfig.dependencies, ), // call onSuccess hook if it exists factory.createIfStatement( diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/bidirectional-relationship.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/bidirectional-relationship.ts index 6a8ac622d..411685b46 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/bidirectional-relationship.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/bidirectional-relationship.ts @@ -91,6 +91,7 @@ function unlinkModelRecordExpression({ associatedFields, importCollection, dataApi, + renderConfigDependencies, }: { modelName: string; primaryKeys: string[]; @@ -99,6 +100,7 @@ function unlinkModelRecordExpression({ associatedFields: string[]; importCollection: ImportCollection; dataApi?: DataApiKind; + renderConfigDependencies?: { [key: string]: string }; }) { if (dataApi === 'GraphQL') { const inputs = [ @@ -117,7 +119,16 @@ function unlinkModelRecordExpression({ factory.createCallExpression( factory.createPropertyAccessExpression(factory.createIdentifier('promises'), factory.createIdentifier('push')), undefined, - [getGraphqlCallExpression(ActionType.UPDATE, modelName, importCollection, { inputs })], + [ + getGraphqlCallExpression( + ActionType.UPDATE, + modelName, + importCollection, + { inputs }, + undefined, + renderConfigDependencies, + ), + ], ), ); } @@ -267,6 +278,7 @@ function linkModelRecordExpression({ associatedPrimaryKeys, associatedFieldsBiDirectionalWith, dataApi, + renderConfigDependencies, }: { importedRelatedModelName: string; relatedRecordToLink: string; @@ -278,6 +290,7 @@ function linkModelRecordExpression({ associatedPrimaryKeys: string[]; associatedFieldsBiDirectionalWith: string[]; dataApi?: DataApiKind; + renderConfigDependencies?: { [key: string]: string }; }) { if (dataApi === 'GraphQL') { const inputs = [ @@ -299,7 +312,16 @@ function linkModelRecordExpression({ factory.createCallExpression( factory.createPropertyAccessExpression(factory.createIdentifier('promises'), factory.createIdentifier('push')), undefined, - [getGraphqlCallExpression(ActionType.UPDATE, importedRelatedModelName, importCollection, { inputs })], + [ + getGraphqlCallExpression( + ActionType.UPDATE, + importedRelatedModelName, + importCollection, + { inputs }, + undefined, + renderConfigDependencies, + ), + ], ), ); } @@ -374,6 +396,7 @@ export function getBiDirectionalRelationshipStatements({ savedRecordName, thisModelPrimaryKeys, dataApi, + renderConfigDependencies, }: { formActionType: 'create' | 'update'; dataSchema: GenericDataSchema; @@ -383,6 +406,7 @@ export function getBiDirectionalRelationshipStatements({ savedRecordName: string; thisModelPrimaryKeys: string[]; dataApi?: DataApiKind; + renderConfigDependencies?: { [key: string]: string }; }) { const getFieldBiDirectionalWithReturnValue = getFieldBiDirectionalWith({ modelName, @@ -474,6 +498,7 @@ export function getBiDirectionalRelationshipStatements({ associatedFields: associatedFieldsBiDirectionalWith, importCollection, dataApi, + renderConfigDependencies, }), ], true, @@ -561,6 +586,7 @@ export function getBiDirectionalRelationshipStatements({ associatedPrimaryKeys: fieldBiDirectionalWithPrimaryKeys, associatedFieldsBiDirectionalWith, dataApi, + renderConfigDependencies, }), factory.createVariableStatement( undefined, @@ -605,6 +631,7 @@ export function getBiDirectionalRelationshipStatements({ associatedFields, importCollection, dataApi, + renderConfigDependencies, }), ], true, diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts index d0ee0b7cc..2408be17a 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts @@ -48,16 +48,25 @@ const getRecordCreateCallExpression = ({ importedModelName, importCollection, dataApi, + renderConfigDependencies, }: { savedObjectName: string; importedModelName: string; importCollection: ImportCollection; dataApi?: DataApiKind; + renderConfigDependencies?: { [key: string]: string }; }) => { if (dataApi === 'GraphQL') { const inputs = [factory.createSpreadAssignment(factory.createIdentifier(savedObjectName))]; - return getGraphqlCallExpression(ActionType.CREATE, importedModelName, importCollection, { inputs }); + return getGraphqlCallExpression( + ActionType.CREATE, + importedModelName, + importCollection, + { inputs }, + undefined, + renderConfigDependencies, + ); } return factory.createCallExpression( @@ -80,6 +89,7 @@ const getRecordUpdateDataStoreCallExpression = ({ fieldConfigs, importCollection, dataApi, + renderConfigDependencies, }: { savedObjectName: string; savedRecordName: string; @@ -89,6 +99,7 @@ const getRecordUpdateDataStoreCallExpression = ({ fieldConfigs: Record<string, FieldConfigMetadata>; importCollection: ImportCollection; dataApi?: DataApiKind; + renderConfigDependencies?: { [key: string]: string }; }) => { const updatedObjectName = 'updated'; // TODO: remove after DataStore addresses issue: https://github.com/aws-amplify/amplify-js/issues/10750 @@ -110,7 +121,14 @@ const getRecordUpdateDataStoreCallExpression = ({ factory.createSpreadAssignment(factory.createIdentifier(savedObjectName)), ]; - return getGraphqlCallExpression(ActionType.UPDATE, importedModelName, importCollection, { inputs }); + return getGraphqlCallExpression( + ActionType.UPDATE, + importedModelName, + importCollection, + { inputs }, + undefined, + renderConfigDependencies, + ); } return factory.createCallExpression( @@ -251,6 +269,7 @@ export const buildExpression = ( dataSchema: GenericDataSchema, importCollection: ImportCollection, dataApi?: DataApiKind, + renderConfigDependencies?: { [key: string]: string }, ): Statement[] => { const modelFieldsObjectName = 'modelFields'; const modelFieldsObjectToSaveName = 'modelFieldsToSave'; @@ -273,6 +292,7 @@ export const buildExpression = ( savedRecordName, thisModelPrimaryKeys, dataApi, + renderConfigDependencies, }), ); if (fieldConfigMetaData.relationship?.type === 'HAS_MANY') { @@ -288,6 +308,7 @@ export const buildExpression = ( savedRecordName, importCollection, dataApi, + renderConfigDependencies, ), ); } else { @@ -300,6 +321,7 @@ export const buildExpression = ( savedRecordName, importCollection, dataApi, + renderConfigDependencies, ), ); } @@ -376,6 +398,7 @@ export const buildExpression = ( fieldConfigs, importCollection, dataApi, + renderConfigDependencies, }); const genericUpdateStatement = relationshipsPromisesAccessStatements.length @@ -402,6 +425,7 @@ export const buildExpression = ( importedModelName, importCollection, dataApi, + renderConfigDependencies, }); const genericCreateStatement = relationshipsPromisesAccessStatements.length ? [ @@ -689,6 +713,7 @@ export const buildUpdateDatastoreQuery = ( importCollection: ImportCollection, primaryKeys: string[], dataApi?: DataApiKind, + renderConfigDependencies?: { [key: string]: string }, ) => { // if there are multiple primaryKeys, it's a composite key and we're using 'id' for a composite key prop const pkPropIdentifier = factory.createIdentifier(primaryKeyPropName); @@ -696,13 +721,20 @@ export const buildUpdateDatastoreQuery = ( const queryCall = dataApi === 'GraphQL' ? wrapInParenthesizedExpression( - getGraphqlCallExpression(ActionType.GET, importedModelName, importCollection, { - inputs: [ - primaryKeys.length > 1 - ? factory.createSpreadAssignment(pkPropIdentifier) - : factory.createPropertyAssignment(factory.createIdentifier(primaryKeys[0]), pkPropIdentifier), - ], - }), + getGraphqlCallExpression( + ActionType.GET, + importedModelName, + importCollection, + { + inputs: [ + primaryKeys.length > 1 + ? factory.createSpreadAssignment(pkPropIdentifier) + : factory.createPropertyAssignment(factory.createIdentifier(primaryKeys[0]), pkPropIdentifier), + ], + }, + undefined, + renderConfigDependencies, + ), ['data', getGraphqlQueryForModel(ActionType.GET, importedModelName)], ) : factory.createAwaitExpression( diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts index 3f6ab63b1..e4ff32874 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts @@ -54,11 +54,14 @@ import { mapFieldArraysToPropertyAssignments, wrapInParenthesizedExpression, } from '../../utils/graphql'; +import { AMPLIFY_JS_V5 } from '../../utils/constants'; +import { getAmplifyJSVersionToRender } from '../../helpers/amplify-js-versioning'; export const buildRelationshipQuery = ( relatedModelName: string, importCollection: ImportCollection, dataApi?: DataApiKind, + renderConfigDependencies?: { [key: string]: string }, ) => { const itemsName = getRecordsName(relatedModelName); @@ -73,7 +76,14 @@ export const buildRelationshipQuery = ( undefined, undefined, wrapInParenthesizedExpression( - getGraphqlCallExpression(ActionType.LIST, relatedModelName, importCollection), + getGraphqlCallExpression( + ActionType.LIST, + relatedModelName, + importCollection, + undefined, + undefined, + renderConfigDependencies, + ), ['data', getGraphqlQueryForModel(ActionType.LIST, relatedModelName), 'items'], ), ), @@ -257,6 +267,7 @@ export const buildManyToManyRelationshipStatements = ( savedModelName: string, importCollection: ImportCollection, dataApi?: DataApiKind, + renderConfigDependencies?: { [key: string]: string }, ) => { let [fieldName] = hasManyFieldConfig; const [, fieldConfigMetaData] = hasManyFieldConfig; @@ -829,6 +840,7 @@ export const buildManyToManyRelationshipStatements = ( thisModelRecord, isGraphql, importCollection, + renderConfigDependencies, ), ), ], @@ -1035,6 +1047,7 @@ export const buildManyToManyRelationshipStatements = ( relatedModelPrimaryKeys, joinTableRelatedModelFields, importCollection, + renderConfigDependencies, ) : getCreateJoinTableExpression( relatedJoinTableName, @@ -1043,6 +1056,7 @@ export const buildManyToManyRelationshipStatements = ( joinTableRelatedModelName, importCollection, dataApi, + renderConfigDependencies, ), ], ), @@ -1070,8 +1084,10 @@ export const buildGetRelationshipModels = ( primaryKeys: string[], importCollection: ImportCollection, dataApi?: DataApiKind, + renderConfigDependencies?: { [key: string]: string }, ) => { const recordIdentifier = factory.createIdentifier('record'); + const amplifyJSVersion = getAmplifyJSVersionToRender(renderConfigDependencies); if (fieldConfigMetaData.relationship?.type === 'HAS_MANY') { const linkedDataName = getLinkedDataName(fieldName); @@ -1108,7 +1124,7 @@ export const buildGetRelationshipModels = ( factory.createAwaitExpression( factory.createCallExpression( factory.createPropertyAccessExpression( - factory.createIdentifier('API'), + factory.createIdentifier(amplifyJSVersion === AMPLIFY_JS_V5 ? 'API' : 'client'), factory.createIdentifier('graphql'), ), undefined, @@ -1320,14 +1336,21 @@ export const buildGetRelationshipModels = ( if (dataApi === 'GraphQL' && fieldConfigMetaData.relationship && !isModelDataType(fieldConfigMetaData)) { const relatedModelName = lowerCaseFirst(fieldConfigMetaData.relationship.relatedModelName); const queryCall = wrapInParenthesizedExpression( - getGraphqlCallExpression(ActionType.GET, fieldConfigMetaData.relationship.relatedModelName, importCollection, { - inputs: [ - factory.createPropertyAssignment( - factory.createIdentifier('id'), - factory.createIdentifier(`${fieldName}Record`), - ), - ], - }), + getGraphqlCallExpression( + ActionType.GET, + fieldConfigMetaData.relationship.relatedModelName, + importCollection, + { + inputs: [ + factory.createPropertyAssignment( + factory.createIdentifier('id'), + factory.createIdentifier(`${fieldName}Record`), + ), + ], + }, + undefined, + renderConfigDependencies, + ), ['data', getGraphqlQueryForModel(ActionType.GET, fieldConfigMetaData.relationship.relatedModelName)], ); @@ -1432,6 +1455,7 @@ export const buildHasManyRelationshipStatements = ( savedModelName: string, importCollection: ImportCollection, dataApi?: DataApiKind, + renderConfigDependencies?: { [key: string]: string }, ) => { let [fieldName] = hasManyFieldConfig; const [, fieldConfigMetaData] = hasManyFieldConfig; @@ -1772,26 +1796,33 @@ export const buildHasManyRelationshipStatements = ( undefined, [ dataApi === 'GraphQL' - ? getGraphqlCallExpression(ActionType.UPDATE, relatedModelName, importCollection, { - inputs: keys - .map((key) => - factory.createPropertyAssignment( - factory.createIdentifier(key), - factory.createPropertyAccessExpression( - factory.createIdentifier('original'), - factory.createIdentifier(key), - ), - ), - ) - .concat( - relatedModelFields.map((key) => + ? getGraphqlCallExpression( + ActionType.UPDATE, + relatedModelName, + importCollection, + { + inputs: keys + .map((key) => factory.createPropertyAssignment( factory.createIdentifier(key), - factory.createNull(), + factory.createPropertyAccessExpression( + factory.createIdentifier('original'), + factory.createIdentifier(key), + ), + ), + ) + .concat( + relatedModelFields.map((key) => + factory.createPropertyAssignment( + factory.createIdentifier(key), + factory.createNull(), + ), ), ), - ), - }) + }, + undefined, + renderConfigDependencies, + ) : getUpdateRelatedModelExpression( keys, thisModelRecord, @@ -1802,6 +1833,7 @@ export const buildHasManyRelationshipStatements = ( dataApi, belongsToFieldOnRelatedModel, true, + renderConfigDependencies, ), ], ), @@ -1849,28 +1881,35 @@ export const buildHasManyRelationshipStatements = ( undefined, [ dataApi === 'GraphQL' - ? getGraphqlCallExpression(ActionType.UPDATE, relatedModelName, importCollection, { - inputs: [ - ...keys.map((key) => - factory.createPropertyAssignment( - factory.createIdentifier(key), - factory.createPropertyAccessExpression( - factory.createIdentifier('original'), + ? getGraphqlCallExpression( + ActionType.UPDATE, + relatedModelName, + importCollection, + { + inputs: [ + ...keys.map((key) => + factory.createPropertyAssignment( factory.createIdentifier(key), + factory.createPropertyAccessExpression( + factory.createIdentifier('original'), + factory.createIdentifier(key), + ), ), ), - ), - ...relatedModelFields.map((relatedModelField) => - factory.createPropertyAssignment( - factory.createIdentifier(relatedModelField), - factory.createPropertyAccessExpression( - factory.createIdentifier(thisModelRecord), - factory.createIdentifier(thisModelPrimaryKeys[0]), + ...relatedModelFields.map((relatedModelField) => + factory.createPropertyAssignment( + factory.createIdentifier(relatedModelField), + factory.createPropertyAccessExpression( + factory.createIdentifier(thisModelRecord), + factory.createIdentifier(thisModelPrimaryKeys[0]), + ), ), ), - ), - ], - }) + ], + }, + undefined, + renderConfigDependencies, + ) : getUpdateRelatedModelExpression( keys, thisModelRecord, @@ -1880,6 +1919,8 @@ export const buildHasManyRelationshipStatements = ( importCollection, dataApi, belongsToFieldOnRelatedModel, + undefined, + renderConfigDependencies, ), ], ), @@ -1903,6 +1944,8 @@ export const buildHasManyRelationshipStatements = ( importCollection, dataApi, belongsToFieldOnRelatedModel, + undefined, + renderConfigDependencies, ); return [ factory.createExpressionStatement( @@ -2034,6 +2077,7 @@ const getUpdateRelatedModelExpression = ( dataApi?: DataApiKind, belongsToFieldOnRelatedModel?: string, setToNull?: boolean, + renderConfigDependencies?: { [key: string]: string }, ) => { /* istanbul ignore next */ if (dataApi === 'GraphQL') { @@ -2076,9 +2120,16 @@ const getUpdateRelatedModelExpression = ( ...statements, ]; - return getGraphqlCallExpression(ActionType.UPDATE, capitalizeFirstLetter(relatedModelName), importCollection, { - inputs, - }); + return getGraphqlCallExpression( + ActionType.UPDATE, + capitalizeFirstLetter(relatedModelName), + importCollection, + { + inputs, + }, + undefined, + renderConfigDependencies, + ); } /** @@ -2137,6 +2188,7 @@ const getCreateJoinTableExpression = ( joinTableRelatedModelName: string, importCollection: ImportCollection, dataApi?: DataApiKind, + renderConfigDependencies?: { [key: string]: string }, ): CallExpression => { /* istanbul ignore next */ if (dataApi === 'GraphQL') { @@ -2150,7 +2202,14 @@ const getCreateJoinTableExpression = ( factory.createShorthandPropertyAssignment(factory.createIdentifier(joinTableRelatedModelName), undefined), ]; - return getGraphqlCallExpression(ActionType.CREATE, relatedJoinTableName, importCollection, { inputs }); + return getGraphqlCallExpression( + ActionType.CREATE, + relatedJoinTableName, + importCollection, + { inputs }, + undefined, + renderConfigDependencies, + ); } return factory.createCallExpression( @@ -2233,6 +2292,7 @@ function buildUnlinkForEachBlock( thisModelRecord: string, isGraphql: boolean, importCollection: ImportCollection, + renderConfigDependencies?: { [key: string]: string }, ) { const recordKeysString = 'recordKeys'; const joinTableFilterExpressions: ObjectLiteralExpression[] = []; @@ -2317,7 +2377,14 @@ function buildUnlinkForEachBlock( undefined, wrapInParenthesizedExpression( isGraphql - ? getGraphqlCallExpression(ActionType.LIST, relatedJoinTableName, importCollection, { filters }) + ? getGraphqlCallExpression( + ActionType.LIST, + relatedJoinTableName, + importCollection, + { filters }, + undefined, + renderConfigDependencies, + ) : factory.createCallExpression( factory.createPropertyAccessExpression( factory.createIdentifier('DataStore'), @@ -2399,20 +2466,27 @@ function buildUnlinkForEachBlock( undefined, [ isGraphql - ? getGraphqlCallExpression(ActionType.DELETE, relatedJoinTableName, importCollection, { - inputs: [ - factory.createPropertyAssignment( - factory.createIdentifier('id'), - factory.createPropertyAccessExpression( - factory.createElementAccessExpression( - factory.createIdentifier(getRecordsName(relatedJoinTableName)), - factory.createIdentifier('i'), - ), + ? getGraphqlCallExpression( + ActionType.DELETE, + relatedJoinTableName, + importCollection, + { + inputs: [ + factory.createPropertyAssignment( factory.createIdentifier('id'), + factory.createPropertyAccessExpression( + factory.createElementAccessExpression( + factory.createIdentifier(getRecordsName(relatedJoinTableName)), + factory.createIdentifier('i'), + ), + factory.createIdentifier('id'), + ), ), - ), - ], - }) + ], + }, + undefined, + renderConfigDependencies, + ) : factory.createCallExpression( factory.createPropertyAccessExpression( factory.createIdentifier('DataStore'), diff --git a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts index f62393a01..02181759b 100644 --- a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts +++ b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts @@ -55,7 +55,7 @@ import { getModelNameProp, lowerCaseFirst, } from '../helpers'; -import { ImportCollection, ImportValue } from '../imports'; +import { ImportCollection, ImportSource, ImportValue } from '../imports'; import { PrimitiveTypeParameter, Primitive, primitiveOverrideProp } from '../primitive'; import { getComponentPropName } from '../react-component-render-helper'; import { ReactOutputManager } from '../react-output-manager'; @@ -63,6 +63,7 @@ import { ReactRenderConfig, scriptKindToFileExtension } from '../react-render-co import { buildPrinter, defaultRenderConfig, + getAmplifyJSClientGenerator, getDeclarationFilename, transpile, } from '../react-studio-template-renderer-helper'; @@ -102,8 +103,9 @@ import { validationResponseType, } from './form-renderer-helper/type-helper'; import { buildSelectedRecordsIdSet } from './form-renderer-helper/model-values'; -import { COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../utils/constants'; +import { AMPLIFY_JS_V6, COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../utils/constants'; import { getFetchRelatedRecordsCallbacks, isGraphqlConfig } from '../utils/graphql'; +import { getAmplifyJSVersionToRender } from '../helpers/amplify-js-versioning'; type RenderComponentOnlyResponse = { compText: string; @@ -239,6 +241,17 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< componentText += propsPrinted; }); + // Amplify JS V6 api + // const client = generateClient(); + if ( + isGraphqlConfig(this.renderConfig.apiConfiguration) && + this.importCollection.hasPackage(ImportSource.AMPLIFY_API) && + getAmplifyJSVersionToRender(this.renderConfig.dependencies) === AMPLIFY_JS_V6 + ) { + const result = printer.printNode(EmitHint.Unspecified, getAmplifyJSClientGenerator(), file); + componentText += result + EOL; + } + if (this.shouldRenderArrayField) { const arrayFieldComponent = printer.printNode(EmitHint.Unspecified, generateArrayFieldComponent(), file); componentText += arrayFieldComponent; @@ -532,6 +545,7 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< this.primaryKeys!, this.importCollection, dataApi, + this.renderConfig.dependencies, ), ); } @@ -552,6 +566,7 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< this.importCollection, this.primaryKeys, dataApi, + this.renderConfig.dependencies, ), [primaryKeyPropName, getModelNameProp(lowerCaseDataTypeName)], ), @@ -654,7 +669,7 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< statements.push( ...[...relatedModelNames].map((relatedModelName) => - buildRelationshipQuery(relatedModelName, this.importCollection, dataApi), + buildRelationshipQuery(relatedModelName, this.importCollection, dataApi, this.renderConfig.dependencies), ), ); } @@ -692,6 +707,7 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< formMetadata.fieldConfigs, this.importCollection, this.renderConfig.apiConfiguration?.dataApi, + this.renderConfig.dependencies, ), ); } diff --git a/packages/codegen-ui-react/lib/imports/import-collection.ts b/packages/codegen-ui-react/lib/imports/import-collection.ts index b9bb9df12..6f6620e72 100644 --- a/packages/codegen-ui-react/lib/imports/import-collection.ts +++ b/packages/codegen-ui-react/lib/imports/import-collection.ts @@ -132,6 +132,10 @@ export class ImportCollection { this.#collection.delete(packageImport); } + hasPackage(packageName: string) { + return this.#collection.has(packageName); + } + getMappedAlias(packageName: string, importName: string) { return this.importAlias.get(packageName)?.get(importName) || importName; } diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts index bc4f08358..e6804651b 100644 --- a/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts +++ b/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts @@ -314,3 +314,21 @@ export const createHookStatement = (variableName: string, methodName: string, pr ), ); }; + +// const client = generateClient(); +export const getAmplifyJSClientGenerator = () => { + return factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('client'), + undefined, + undefined, + factory.createCallExpression(factory.createIdentifier('generateClient'), undefined, []), + ), + ], + ts.NodeFlags.Const, + ), + ); +}; diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts index 6d61ea180..dd8ecf024 100644 --- a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts @@ -92,6 +92,7 @@ import { buildBaseCollectionVariableStatement, createHookStatement, buildSortFunction, + getAmplifyJSClientGenerator, } from './react-studio-template-renderer-helper'; import { Primitive, @@ -122,6 +123,8 @@ import { } from './helpers'; import { addUseEffectWrapper } from './utils/generate-react-hooks'; import { ActionType, getGraphqlCallExpression, getGraphqlQueryForModel, isGraphqlConfig } from './utils/graphql'; +import { AMPLIFY_JS_V5, AMPLIFY_JS_V6 } from './utils/constants'; +import { getAmplifyJSVersionToRender } from './helpers/amplify-js-versioning'; export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer< string, @@ -289,6 +292,17 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer }); } + // Amplify JS V6 api + // const client = generateClient(); + if ( + isGraphqlConfig(this.renderConfig.apiConfiguration) && + this.importCollection.hasPackage(ImportSource.AMPLIFY_API) && + getAmplifyJSVersionToRender(this.renderConfig.dependencies) === AMPLIFY_JS_V6 + ) { + const result = printer.printNode(EmitHint.Unspecified, getAmplifyJSClientGenerator(), file); + componentText += result + EOL; + } + const result = printer.printNode(EmitHint.Unspecified, wrappedFunction, file); componentText += result; @@ -1349,13 +1363,20 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer factory.createElementAccessExpression( factory.createPropertyAccessExpression( this.renderConfig.apiConfiguration?.dataApi === 'GraphQL' - ? getGraphqlCallExpression(ActionType.LIST, modelName, this.importCollection, { - inputs: [ - factory.createSpreadAssignment( - factory.createIdentifier(this.getFilterObjName(propName)), - ), - ], - }) + ? getGraphqlCallExpression( + ActionType.LIST, + modelName, + this.importCollection, + { + inputs: [ + factory.createSpreadAssignment( + factory.createIdentifier(this.getFilterObjName(propName)), + ), + ], + }, + undefined, + this.renderConfig.dependencies, + ) : this.buildUseDataStoreBindingCall('collection', modelName, this.getFilterName(propName)), factory.createIdentifier('items'), ), @@ -1601,6 +1622,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer identifier, this.importCollection, this.renderConfig.apiConfiguration?.dataApi, + this.renderConfig.dependencies, ), ); } @@ -1903,6 +1925,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer const loadedFields: ShorthandPropertyAssignment[] = []; const loadedFieldNames: string[] = []; const loadedFieldStatements: Statement[] = []; + const amplifyJSVersion = getAmplifyJSVersionToRender(this.renderConfig.dependencies); Object.entries(component.collectionProperties).forEach((collectionProp) => { const [, { model: modelName, predicate }] = collectionProp; @@ -1944,7 +1967,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer factory.createAwaitExpression( factory.createCallExpression( factory.createPropertyAccessExpression( - factory.createIdentifier('API'), + factory.createIdentifier(amplifyJSVersion === AMPLIFY_JS_V5 ? 'API' : 'client'), factory.createIdentifier('graphql'), ), undefined, @@ -2105,7 +2128,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer factory.createAwaitExpression( factory.createCallExpression( factory.createPropertyAccessExpression( - factory.createIdentifier('API'), + factory.createIdentifier(amplifyJSVersion === AMPLIFY_JS_V5 ? 'API' : 'client'), factory.createIdentifier('graphql'), ), undefined, diff --git a/packages/codegen-ui-react/lib/utils/graphql.ts b/packages/codegen-ui-react/lib/utils/graphql.ts index 1d192e73f..34033ff9c 100644 --- a/packages/codegen-ui-react/lib/utils/graphql.ts +++ b/packages/codegen-ui-react/lib/utils/graphql.ts @@ -25,12 +25,14 @@ import { SyntaxKind, factory, } from 'typescript'; -import { ImportCollection, ImportValue } from '../imports'; +import { ImportCollection } from '../imports'; import { capitalizeFirstLetter, getSetNameIdentifier, lowerCaseFirst } from '../helpers'; import { isBoundProperty, isConcatenatedProperty } from '../react-component-render-helper'; import { Primitive } from '../primitive'; import { DataApiKind, DataStoreRenderConfig, GraphqlRenderConfig, NoApiRenderConfig } from '../react-render-config'; import { isModelDataType } from '../forms/form-renderer-helper/render-checkers'; +import { AMPLIFY_JS_V5 } from './constants'; +import { getAmplifyJSAPIImport, getAmplifyJSVersionToRender } from '../helpers/amplify-js-versioning'; export enum ActionType { CREATE = 'create', @@ -99,6 +101,7 @@ export const getGraphqlCallExpression = ( } | ObjectLiteralElementLike[], byFieldName?: string, + renderConfigDependencies?: { [key: string]: string }, ): CallExpression => { const query = getGraphqlQueryForModel(action, model, byFieldName); const graphqlVariables: ObjectLiteralElementLike[] = []; @@ -113,7 +116,8 @@ export const getGraphqlCallExpression = ( ), ]; - importCollection.addMappedImport(ImportValue.API); + const mappedImport = getAmplifyJSAPIImport(renderConfigDependencies); + importCollection.addMappedImport(mappedImport); if (isGraphqlQueryAction(action)) { importCollection.addGraphqlQueryImport(query); @@ -159,9 +163,12 @@ export const getGraphqlCallExpression = ( } importCollection.addModelImport(model); - + const amplifyJSVersion = getAmplifyJSVersionToRender(renderConfigDependencies); return factory.createCallExpression( - factory.createPropertyAccessExpression(factory.createIdentifier('API'), factory.createIdentifier('graphql')), + factory.createPropertyAccessExpression( + factory.createIdentifier(amplifyJSVersion === AMPLIFY_JS_V5 ? 'API' : 'client'), + factory.createIdentifier('graphql'), + ), undefined, [factory.createObjectLiteralExpression(graphqlOptions, true)], ); @@ -199,6 +206,7 @@ export const getGraphQLJoinTableCreateExpression = ( relatedModelPrimaryKeys: string[], joinTableRelatedModelFields: string[], importCollection: ImportCollection, + renderConfigDependencies?: { [key: string]: string }, ) => { const thisModelAssignments = mapFieldArraysToPropertyAssignments( joinTableThisModelFields, @@ -211,9 +219,16 @@ export const getGraphQLJoinTableCreateExpression = ( relatedModelRecord, ); - return getGraphqlCallExpression(ActionType.CREATE, relatedJoinTableName, importCollection, { - inputs: [...thisModelAssignments, ...relatedModelAssignments], - }); + return getGraphqlCallExpression( + ActionType.CREATE, + relatedJoinTableName, + importCollection, + { + inputs: [...thisModelAssignments, ...relatedModelAssignments], + }, + undefined, + renderConfigDependencies, + ); }; const fetchRelatedRecordEffect = (fetchRecordFunctions: string[]) => { @@ -254,6 +269,7 @@ export const getFetchRelatedRecordsCallbacks = ( fieldConfigs: Record<string, FieldConfigMetadata>, importCollection: ImportCollection, dataApi: DataApiKind = 'DataStore', + renderConfigDependencies?: { [key: string]: string }, ) => { const initalFetchRecords: string[] = []; const fetchRecordsStatements = Object.entries(fieldConfigs).reduce<Statement[]>( @@ -464,6 +480,8 @@ export const getFetchRelatedRecordsCallbacks = ( undefined, ), ], + undefined, + renderConfigDependencies, ), [ 'data', diff --git a/packages/codegen-ui-react/lib/workflow/action.ts b/packages/codegen-ui-react/lib/workflow/action.ts index 9ed3e024a..a2cff89ad 100644 --- a/packages/codegen-ui-react/lib/workflow/action.ts +++ b/packages/codegen-ui-react/lib/workflow/action.ts @@ -120,13 +120,14 @@ export function buildUseActionStatement( identifier: string, importCollection: ImportCollection, apiKind: DataApiKind = 'DataStore', + renderConfigDependencies?: { [key: string]: string }, ): Statement { if (isMutationAction(action)) { return buildMutationActionStatement(componentMetadata, action, identifier); } if (isDataAction(action) && apiKind === 'GraphQL') { - return buildGraphqlCallback(componentMetadata, action, identifier, importCollection); + return buildGraphqlCallback(componentMetadata, action, identifier, importCollection, renderConfigDependencies); } const actionHookImportValue = getActionHookImportValue(action.action); @@ -156,6 +157,7 @@ export function buildGraphqlCallback( action: DataAction, identifier: string, importCollection: ImportCollection, + renderConfigDependencies?: { [key: string]: string }, ) { const inputKeys = []; @@ -194,6 +196,8 @@ export function buildGraphqlCallback( action.parameters.model, importCollection, { inputs: inputKeys }, + undefined, + renderConfigDependencies, ), ), ),