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 996dba1c8..301f75de0 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 @@ -3440,7 +3440,7 @@ export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with belongsTo relationship 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with array of Enums 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -3452,6 +3452,7 @@ import { Grid, Icon, ScrollView, + SelectField, Text, TextField, useTheme, @@ -3460,7 +3461,7 @@ import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; -import { Member, Team as Team0 } from \\"../models\\"; +import { Tag, Post, TagPost } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ @@ -3604,7 +3605,7 @@ function ArrayField({ ); } -export default function MyMemberForm(props) { +export default function TagCreateForm(props) { const { clearOnSuccess = true, onSuccess, @@ -3617,33 +3618,49 @@ export default function MyMemberForm(props) { ...rest } = props; const initialValues = { - name: undefined, - Team: undefined, + label: undefined, + Posts: [], + statuses: [], }; - const [name, setName] = React.useState(initialValues.name); - const [Team, setTeam] = React.useState(initialValues.Team); + const [label, setLabel] = React.useState(initialValues.label); + const [Posts, setPosts] = React.useState(initialValues.Posts); + const [statuses, setStatuses] = React.useState(initialValues.statuses); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setName(initialValues.name); - setTeam(initialValues.Team); - setCurrentTeamValue(undefined); - setCurrentTeamDisplayValue(\\"\\"); + setLabel(initialValues.label); + setPosts(initialValues.Posts); + setCurrentPostsValue(undefined); + setCurrentPostsDisplayValue(\\"\\"); + setStatuses(initialValues.statuses); + setCurrentStatusesValue(undefined); setErrors({}); }; - const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = + const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = React.useState(\\"\\"); - const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); - const TeamRef = React.createRef(); - const teamRecords = useDataStoreBinding({ + const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); + const PostsRef = React.createRef(); + const [currentStatusesValue, setCurrentStatusesValue] = + React.useState(undefined); + const statusesRef = React.createRef(); + const postRecords = useDataStoreBinding({ type: \\"collection\\", - model: Team0, + model: Post, }).items; const getDisplayValue = { - Team: (record) => record?.name, + Posts: (record) => record?.title, + statuses: (record) => { + const enumDisplayValueMap = { + PENDING: \\"Pending\\", + POSTED: \\"Posted\\", + IN_REVIEW: \\"In review\\", + }; + return enumDisplayValueMap[record]; + }, }; const validations = { - name: [], - Team: [], + label: [], + Posts: [], + statuses: [], }; const runValidationTasks = async ( fieldName, @@ -3670,8 +3687,9 @@ export default function MyMemberForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - name, - Team, + label, + Posts, + statuses, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -3704,7 +3722,20 @@ export default function MyMemberForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save(new Member(modelFields)); + const tag = await DataStore.save(new Tag(modelFields)); + await Promise.all( + Posts.reduce((promises, post) => { + promises.push( + DataStore.save( + new TagPost({ + tag, + post, + }) + ) + ); + return promises; + }, []) + ); if (onSuccess) { onSuccess(modelFields); } @@ -3718,165 +3749,229 @@ export default function MyMemberForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"MyMemberForm\\")} + {...getOverrideProps(overrides, \\"TagCreateForm\\")} > - - - - - - - { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - Team, + 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, - Team: value, + label, + Posts: values, + statuses, }; const result = onChange(modelFields); - value = result?.Team ?? value; + values = result?.Posts ?? values; } - setTeam(value); - setCurrentTeamValue(undefined); - setCurrentTeamDisplayValue(\\"\\"); + setPosts(values); + setCurrentPostsValue(undefined); + setCurrentPostsDisplayValue(\\"\\"); }} - currentFieldValue={currentTeamValue} - label={\\"Team Label\\"} - items={Team ? [Team] : []} - hasError={errors.Team?.hasError} - getBadgeText={getDisplayValue.Team} + currentFieldValue={currentPostsValue} + label={\\"Posts\\"} + items={Posts} + hasError={errors.Posts?.hasError} + getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => - setCurrentTeamDisplayValue(getDisplayValue.Team(model)) + setCurrentPostsDisplayValue(getDisplayValue.Posts(model)) } - inputFieldRef={TeamRef} + inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > ({ + value={currentPostsDisplayValue} + options={postRecords.map((r) => ({ id: r.id, - label: getDisplayValue.Team?.(r) ?? r.id, + label: getDisplayValue.Posts?.(r) ?? r.id, }))} onSelect={({ id, label }) => { - setCurrentTeamValue(teamRecords.find((r) => r.id === id)); - setCurrentTeamDisplayValue(label); + setCurrentPostsValue(postRecords.find((r) => r.id === id)); + setCurrentPostsDisplayValue(label); }} onClear={() => { - setCurrentTeamDisplayValue(\\"\\"); + setCurrentPostsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - if (errors.Team?.hasError) { - runValidationTasks(\\"Team\\", value); + if (errors.Posts?.hasError) { + runValidationTasks(\\"Posts\\", value); } - setCurrentTeamDisplayValue(value); - setCurrentTeamValue(undefined); + setCurrentPostsDisplayValue(value); + setCurrentPostsValue(undefined); }} - onBlur={() => runValidationTasks(\\"Team\\", Team)} - errorMessage={errors.Team?.errorMessage} - hasError={errors.Team?.hasError} - ref={TeamRef} + onBlur={() => runValidationTasks(\\"Posts\\", currentPostsValue)} + errorMessage={errors.Posts?.errorMessage} + hasError={errors.Posts?.hasError} + ref={PostsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Team\\")} + {...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(undefined); + }} + currentFieldValue={currentStatusesValue} + label={\\"Statuses\\"} + items={statuses} + hasError={errors.statuses?.hasError} + getBadgeText={getDisplayValue.statuses} + setFieldValue={setCurrentStatusesDisplayValue} + inputFieldRef={statusesRef} + defaultFieldValue={undefined} + > + { + let { value } = e.target; + if (errors.statuses?.hasError) { + runValidationTasks(\\"statuses\\", value); + } + setCurrentStatusesValue(value); + }} + onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} + errorMessage={errors.statuses?.errorMessage} + hasError={errors.statuses?.hasError} + ref={statusesRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"statuses\\")} + > + + + + + + + + + + + + ); } " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with belongsTo relationship 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with array of Enums 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 { Team as Team0 } from \\"../models\\"; +import { Post } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type MyMemberFormInputValues = { - name?: string; - Team?: Team0; +export declare type TagCreateFormInputValues = { + label?: string; + Posts?: Post[]; + statuses?: string[]; }; -export declare type MyMemberFormValidationValues = { - name?: ValidationFunction; - Team?: ValidationFunction; +export declare type TagCreateFormValidationValues = { + label?: ValidationFunction; + Posts?: ValidationFunction; + statuses?: ValidationFunction; }; export declare type FormProps = Partial & React.DOMAttributes; -export declare type MyMemberFormOverridesProps = { - MyMemberFormGrid?: FormProps; - name?: FormProps; - Team?: FormProps; +export declare type TagCreateFormOverridesProps = { + TagCreateFormGrid?: FormProps; + label?: FormProps; + Posts?: FormProps; + statuses?: FormProps; } & EscapeHatchProps; -export declare type MyMemberFormProps = React.PropsWithChildren<{ - overrides?: MyMemberFormOverridesProps | undefined | null; +export declare type TagCreateFormProps = React.PropsWithChildren<{ + overrides?: TagCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onSuccess?: (fields: MyMemberFormInputValues) => void; - onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; + onSuccess?: (fields: TagCreateFormInputValues) => void; + onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onValidate?: MyMemberFormValidationValues; + onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; + onValidate?: TagCreateFormValidationValues; } & React.CSSProperties>; -export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; +export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -3896,7 +3991,7 @@ import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; -import { Book, Author } from \\"../models\\"; +import { Member, Team as Team0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ @@ -4040,7 +4135,7 @@ function ArrayField({ ); } -export default function BookCreateForm(props) { +export default function MyMemberForm(props) { const { clearOnSuccess = true, onSuccess, @@ -4054,37 +4149,32 @@ export default function BookCreateForm(props) { } = props; const initialValues = { name: undefined, - primaryAuthor: undefined, + Team: undefined, }; const [name, setName] = React.useState(initialValues.name); - const [primaryAuthor, setPrimaryAuthor] = React.useState( - initialValues.primaryAuthor - ); + const [Team, setTeam] = React.useState(initialValues.Team); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); - setPrimaryAuthor(initialValues.primaryAuthor); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setTeam(initialValues.Team); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(\\"\\"); setErrors({}); }; - const [ - currentPrimaryAuthorDisplayValue, - setCurrentPrimaryAuthorDisplayValue, - ] = React.useState(\\"\\"); - const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = - React.useState(undefined); - const primaryAuthorRef = React.createRef(); - const authorRecords = useDataStoreBinding({ + const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = + React.useState(\\"\\"); + const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); + const TeamRef = React.createRef(); + const teamRecords = useDataStoreBinding({ type: \\"collection\\", - model: Author, + model: Team0, }).items; const getDisplayValue = { - primaryAuthor: (record) => record?.name, + Team: (record) => record?.name, }; const validations = { name: [], - primaryAuthor: [], + Team: [], }; const runValidationTasks = async ( fieldName, @@ -4112,7 +4202,7 @@ export default function BookCreateForm(props) { event.preventDefault(); let modelFields = { name, - primaryAuthor, + Team, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -4145,7 +4235,7 @@ export default function BookCreateForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save(new Book(modelFields)); + await DataStore.save(new Member(modelFields)); if (onSuccess) { onSuccess(modelFields); } @@ -4159,7 +4249,7 @@ export default function BookCreateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"BookCreateForm\\")} + {...getOverrideProps(overrides, \\"MyMemberForm\\")} > - setCurrentPrimaryAuthorDisplayValue( - getDisplayValue.primaryAuthor(model) - ) + setCurrentTeamDisplayValue(getDisplayValue.Team(model)) } - inputFieldRef={primaryAuthorRef} + inputFieldRef={TeamRef} defaultFieldValue={\\"\\"} > ({ + value={currentTeamDisplayValue} + options={teamRecords.map((r) => ({ id: r.id, - label: getDisplayValue.primaryAuthor?.(r) ?? r.id, + label: getDisplayValue.Team?.(r) ?? r.id, }))} onSelect={({ id, label }) => { - setCurrentPrimaryAuthorValue( - authorRecords.find((r) => r.id === id) - ); - setCurrentPrimaryAuthorDisplayValue(label); + setCurrentTeamValue(teamRecords.find((r) => r.id === id)); + setCurrentTeamDisplayValue(label); }} onClear={() => { - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setCurrentTeamDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - if (errors.primaryAuthor?.hasError) { - runValidationTasks(\\"primaryAuthor\\", value); + if (errors.Team?.hasError) { + runValidationTasks(\\"Team\\", value); } - setCurrentPrimaryAuthorDisplayValue(value); - setCurrentPrimaryAuthorValue(undefined); + setCurrentTeamDisplayValue(value); + setCurrentTeamValue(undefined); }} - onBlur={() => runValidationTasks(\\"primaryAuthor\\", primaryAuthor)} - errorMessage={errors.primaryAuthor?.errorMessage} - hasError={errors.primaryAuthor?.hasError} - ref={primaryAuthorRef} + onBlur={() => runValidationTasks(\\"Team\\", Team)} + errorMessage={errors.Team?.errorMessage} + hasError={errors.Team?.hasError} + ref={TeamRef} labelHidden={true} - {...getOverrideProps(overrides, \\"primaryAuthor\\")} + {...getOverrideProps(overrides, \\"Team\\")} > @@ -4282,46 +4368,46 @@ export default function BookCreateForm(props) { " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 2`] = ` +exports[`amplify form renderer tests datastore 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 { Author } from \\"../models\\"; +import { Team as Team0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type BookCreateFormInputValues = { +export declare type MyMemberFormInputValues = { name?: string; - primaryAuthor?: Author; + Team?: Team0; }; -export declare type BookCreateFormValidationValues = { +export declare type MyMemberFormValidationValues = { name?: ValidationFunction; - primaryAuthor?: ValidationFunction; + Team?: ValidationFunction; }; export declare type FormProps = Partial & React.DOMAttributes; -export declare type BookCreateFormOverridesProps = { - BookCreateFormGrid?: FormProps; +export declare type MyMemberFormOverridesProps = { + MyMemberFormGrid?: FormProps; name?: FormProps; - primaryAuthor?: FormProps; + Team?: FormProps; } & EscapeHatchProps; -export declare type BookCreateFormProps = React.PropsWithChildren<{ - overrides?: BookCreateFormOverridesProps | undefined | null; +export declare type MyMemberFormProps = React.PropsWithChildren<{ + overrides?: MyMemberFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onSuccess?: (fields: BookCreateFormInputValues) => void; - onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onSuccess?: (fields: MyMemberFormInputValues) => void; + onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onValidate?: BookCreateFormValidationValues; + onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onValidate?: MyMemberFormValidationValues; } & React.CSSProperties>; -export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; +export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with manyToMany relationship 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -4341,7 +4427,7 @@ import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; -import { Tag, Post, TagPost } from \\"../models\\"; +import { Book, Author } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ @@ -4485,7 +4571,7 @@ function ArrayField({ ); } -export default function TagCreateForm(props) { +export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, @@ -4498,33 +4584,38 @@ export default function TagCreateForm(props) { ...rest } = props; const initialValues = { - label: undefined, - Posts: [], + name: undefined, + primaryAuthor: undefined, }; - const [label, setLabel] = React.useState(initialValues.label); - const [Posts, setPosts] = React.useState(initialValues.Posts); + const [name, setName] = React.useState(initialValues.name); + const [primaryAuthor, setPrimaryAuthor] = React.useState( + initialValues.primaryAuthor + ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setLabel(initialValues.label); - setPosts(initialValues.Posts); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); + setName(initialValues.name); + setPrimaryAuthor(initialValues.primaryAuthor); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); setErrors({}); }; - const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = - React.useState(\\"\\"); - const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); - const PostsRef = React.createRef(); - const postRecords = useDataStoreBinding({ + const [ + currentPrimaryAuthorDisplayValue, + setCurrentPrimaryAuthorDisplayValue, + ] = React.useState(\\"\\"); + const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = + React.useState(undefined); + const primaryAuthorRef = React.createRef(); + const authorRecords = useDataStoreBinding({ type: \\"collection\\", - model: Post, + model: Author, }).items; const getDisplayValue = { - Posts: (record) => record?.title, + primaryAuthor: (record) => record?.name, }; const validations = { - label: [], - Posts: [], + name: [], + primaryAuthor: [], }; const runValidationTasks = async ( fieldName, @@ -4551,8 +4642,8 @@ export default function TagCreateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - label, - Posts, + name, + primaryAuthor, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -4585,20 +4676,7 @@ export default function TagCreateForm(props) { modelFields = onSubmit(modelFields); } try { - const tag = await DataStore.save(new Tag(modelFields)); - await Promise.all( - Posts.reduce((promises, post) => { - promises.push( - DataStore.save( - new TagPost({ - tag, - post, - }) - ) - ); - return promises; - }, []) - ); + await DataStore.save(new Book(modelFields)); if (onSuccess) { onSuccess(modelFields); } @@ -4612,164 +4690,169 @@ export default function TagCreateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"TagCreateForm\\")} + {...getOverrideProps(overrides, \\"BookCreateForm\\")} > + + + + + + + { let { value } = e.target; if (onChange) { const modelFields = { - label: value, - Posts, + name: value, + primaryAuthor, }; const result = onChange(modelFields); - value = result?.label ?? value; + value = result?.name ?? value; } - if (errors.label?.hasError) { - runValidationTasks(\\"label\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setLabel(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"label\\", label)} - errorMessage={errors.label?.errorMessage} - hasError={errors.label?.hasError} - {...getOverrideProps(overrides, \\"label\\")} - > - { - let values = items; + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} + > + { + let value = items[0]; if (onChange) { const modelFields = { - label, - Posts: values, + name, + primaryAuthor: value, }; const result = onChange(modelFields); - values = result?.Posts ?? values; + value = result?.primaryAuthor ?? value; } - setPosts(values); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); + setPrimaryAuthor(value); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} - currentFieldValue={currentPostsValue} - label={\\"Posts\\"} - items={Posts} - hasError={errors.Posts?.hasError} - getBadgeText={getDisplayValue.Posts} + currentFieldValue={currentPrimaryAuthorValue} + label={\\"Primary author\\"} + items={primaryAuthor ? [primaryAuthor] : []} + hasError={errors.primaryAuthor?.hasError} + getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => - setCurrentPostsDisplayValue(getDisplayValue.Posts(model)) + setCurrentPrimaryAuthorDisplayValue( + getDisplayValue.primaryAuthor(model) + ) } - inputFieldRef={PostsRef} + inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > ({ + value={currentPrimaryAuthorDisplayValue} + options={authorRecords.map((r) => ({ id: r.id, - label: getDisplayValue.Posts?.(r) ?? r.id, + label: getDisplayValue.primaryAuthor?.(r) ?? r.id, }))} onSelect={({ id, label }) => { - setCurrentPostsValue(postRecords.find((r) => r.id === id)); - setCurrentPostsDisplayValue(label); + setCurrentPrimaryAuthorValue( + authorRecords.find((r) => r.id === id) + ); + setCurrentPrimaryAuthorDisplayValue(label); }} onClear={() => { - setCurrentPostsDisplayValue(\\"\\"); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - if (errors.Posts?.hasError) { - runValidationTasks(\\"Posts\\", value); + if (errors.primaryAuthor?.hasError) { + runValidationTasks(\\"primaryAuthor\\", value); } - setCurrentPostsDisplayValue(value); - setCurrentPostsValue(undefined); + setCurrentPrimaryAuthorDisplayValue(value); + setCurrentPrimaryAuthorValue(undefined); }} - onBlur={() => runValidationTasks(\\"Posts\\", currentPostsValue)} - errorMessage={errors.Posts?.errorMessage} - hasError={errors.Posts?.hasError} - ref={PostsRef} + onBlur={() => runValidationTasks(\\"primaryAuthor\\", primaryAuthor)} + errorMessage={errors.primaryAuthor?.errorMessage} + hasError={errors.primaryAuthor?.hasError} + ref={primaryAuthorRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Posts\\")} + {...getOverrideProps(overrides, \\"primaryAuthor\\")} > - - - - - - - ); } " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with manyToMany relationship 2`] = ` +exports[`amplify form renderer tests datastore 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 { Post } from \\"../models\\"; +import { Author } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type TagCreateFormInputValues = { - label?: string; - Posts?: Post[]; +export declare type BookCreateFormInputValues = { + name?: string; + primaryAuthor?: Author; }; -export declare type TagCreateFormValidationValues = { - label?: ValidationFunction; - Posts?: ValidationFunction; +export declare type BookCreateFormValidationValues = { + name?: ValidationFunction; + primaryAuthor?: ValidationFunction; }; export declare type FormProps = Partial & React.DOMAttributes; -export declare type TagCreateFormOverridesProps = { - TagCreateFormGrid?: FormProps; - label?: FormProps; - Posts?: FormProps; +export declare type BookCreateFormOverridesProps = { + BookCreateFormGrid?: FormProps; + name?: FormProps; + primaryAuthor?: FormProps; } & EscapeHatchProps; -export declare type TagCreateFormProps = React.PropsWithChildren<{ - overrides?: TagCreateFormOverridesProps | undefined | null; +export declare type BookCreateFormProps = React.PropsWithChildren<{ + overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; - onSuccess?: (fields: TagCreateFormInputValues) => void; - onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onSuccess?: (fields: BookCreateFormInputValues) => void; + onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; - onValidate?: TagCreateFormValidationValues; + onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; -export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; +export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with manyToMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -4781,6 +4864,7 @@ import { Grid, Icon, ScrollView, + SelectField, Text, TextField, useTheme, @@ -4789,7 +4873,7 @@ import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; -import { Book, Author, Title } from \\"../models\\"; +import { Tag, Post, TagPost } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ @@ -4933,7 +5017,7 @@ function ArrayField({ ); } -export default function BookCreateForm(props) { +export default function TagCreateForm(props) { const { clearOnSuccess = true, onSuccess, @@ -4946,56 +5030,49 @@ export default function BookCreateForm(props) { ...rest } = props; const initialValues = { - name: undefined, - primaryAuthor: undefined, - primaryTitle: undefined, + label: undefined, + Posts: [], + statuses: [], }; - const [name, setName] = React.useState(initialValues.name); - const [primaryAuthor, setPrimaryAuthor] = React.useState( - initialValues.primaryAuthor - ); - const [primaryTitle, setPrimaryTitle] = React.useState( - initialValues.primaryTitle - ); + const [label, setLabel] = React.useState(initialValues.label); + const [Posts, setPosts] = React.useState(initialValues.Posts); + const [statuses, setStatuses] = React.useState(initialValues.statuses); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setName(initialValues.name); - setPrimaryAuthor(initialValues.primaryAuthor); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); - setPrimaryTitle(initialValues.primaryTitle); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(\\"\\"); + setLabel(initialValues.label); + setPosts(initialValues.Posts); + setCurrentPostsValue(undefined); + setCurrentPostsDisplayValue(\\"\\"); + setStatuses(initialValues.statuses); + setCurrentStatusesValue(undefined); setErrors({}); }; - const [ - currentPrimaryAuthorDisplayValue, - setCurrentPrimaryAuthorDisplayValue, - ] = React.useState(\\"\\"); - const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = - React.useState(undefined); - const primaryAuthorRef = React.createRef(); - const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = + const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = React.useState(\\"\\"); - const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = + const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); + const PostsRef = React.createRef(); + const [currentStatusesValue, setCurrentStatusesValue] = React.useState(undefined); - const primaryTitleRef = React.createRef(); - const authorRecords = useDataStoreBinding({ - type: \\"collection\\", - model: Author, - }).items; - const titleRecords = useDataStoreBinding({ + const statusesRef = React.createRef(); + const postRecords = useDataStoreBinding({ type: \\"collection\\", - model: Title, + model: Post, }).items; const getDisplayValue = { - primaryAuthor: (record) => record?.name, - primaryTitle: (record) => record?.name, + Posts: (record) => record?.title, + statuses: (record) => { + const enumDisplayValueMap = { + PENDING: \\"Pending\\", + POSTED: \\"Posted\\", + IN_REVIEW: \\"In review\\", + }; + return enumDisplayValueMap[record]; + }, }; const validations = { - name: [], - primaryAuthor: [], - primaryTitle: [], + label: [], + Posts: [], + statuses: [], }; const runValidationTasks = async ( fieldName, @@ -5022,9 +5099,9 @@ export default function BookCreateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - name, - primaryAuthor, - primaryTitle, + label, + Posts, + statuses, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -5057,7 +5134,20 @@ export default function BookCreateForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save(new Book(modelFields)); + const tag = await DataStore.save(new Tag(modelFields)); + await Promise.all( + Posts.reduce((promises, post) => { + promises.push( + DataStore.save( + new TagPost({ + tag, + post, + }) + ) + ); + return promises; + }, []) + ); if (onSuccess) { onSuccess(modelFields); } @@ -5071,315 +5161,455 @@ export default function BookCreateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"BookCreateForm\\")} + {...getOverrideProps(overrides, \\"TagCreateForm\\")} > - - - - - - - { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - primaryAuthor, - primaryTitle, + 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, - primaryTitle, + 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} - getBadgeText={getDisplayValue.primaryAuthor} + currentFieldValue={currentPostsValue} + label={\\"Posts\\"} + items={Posts} + hasError={errors.Posts?.hasError} + getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => - setCurrentPrimaryAuthorDisplayValue( - getDisplayValue.primaryAuthor(model) - ) + setCurrentPostsDisplayValue(getDisplayValue.Posts(model)) } - inputFieldRef={primaryAuthorRef} + inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > ({ + value={currentPostsDisplayValue} + options={postRecords.map((r) => ({ id: r.id, - label: getDisplayValue.primaryAuthor?.(r) ?? r.id, + label: getDisplayValue.Posts?.(r) ?? r.id, }))} onSelect={({ id, label }) => { - setCurrentPrimaryAuthorValue( - authorRecords.find((r) => r.id === id) - ); - setCurrentPrimaryAuthorDisplayValue(label); + setCurrentPostsValue(postRecords.find((r) => r.id === id)); + setCurrentPostsDisplayValue(label); }} onClear={() => { - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setCurrentPostsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - if (errors.primaryAuthor?.hasError) { - runValidationTasks(\\"primaryAuthor\\", value); + if (errors.Posts?.hasError) { + runValidationTasks(\\"Posts\\", value); } - setCurrentPrimaryAuthorDisplayValue(value); - setCurrentPrimaryAuthorValue(undefined); + setCurrentPostsDisplayValue(value); + setCurrentPostsValue(undefined); }} - onBlur={() => runValidationTasks(\\"primaryAuthor\\", primaryAuthor)} - errorMessage={errors.primaryAuthor?.errorMessage} - hasError={errors.primaryAuthor?.hasError} - ref={primaryAuthorRef} + onBlur={() => runValidationTasks(\\"Posts\\", currentPostsValue)} + errorMessage={errors.Posts?.errorMessage} + hasError={errors.Posts?.hasError} + ref={PostsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"primaryAuthor\\")} + {...getOverrideProps(overrides, \\"Posts\\")} > { - let value = items[0]; + let values = items; if (onChange) { const modelFields = { - name, - primaryAuthor, - primaryTitle: value, + label, + Posts, + statuses: values, }; const result = onChange(modelFields); - value = result?.primaryTitle ?? value; + values = result?.statuses ?? values; } - setPrimaryTitle(value); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(\\"\\"); + setStatuses(values); + setCurrentStatusesValue(undefined); }} - currentFieldValue={currentPrimaryTitleValue} - label={\\"Primary title\\"} - items={primaryTitle ? [primaryTitle] : []} - hasError={errors.primaryTitle?.hasError} - getBadgeText={getDisplayValue.primaryTitle} - setFieldValue={(model) => - setCurrentPrimaryTitleDisplayValue( - getDisplayValue.primaryTitle(model) - ) - } - inputFieldRef={primaryTitleRef} - defaultFieldValue={\\"\\"} + currentFieldValue={currentStatusesValue} + label={\\"Statuses\\"} + items={statuses} + hasError={errors.statuses?.hasError} + getBadgeText={getDisplayValue.statuses} + setFieldValue={setCurrentStatusesDisplayValue} + inputFieldRef={statusesRef} + defaultFieldValue={undefined} > - ({ - id: r.id, - label: getDisplayValue.primaryTitle?.(r) ?? r.id, - }))} - onSelect={({ id, label }) => { - setCurrentPrimaryTitleValue(titleRecords.find((r) => r.id === id)); - setCurrentPrimaryTitleDisplayValue(label); - }} - onClear={() => { - setCurrentPrimaryTitleDisplayValue(\\"\\"); - }} + { let { value } = e.target; - if (errors.primaryTitle?.hasError) { - runValidationTasks(\\"primaryTitle\\", value); + if (errors.statuses?.hasError) { + runValidationTasks(\\"statuses\\", value); } - setCurrentPrimaryTitleDisplayValue(value); - setCurrentPrimaryTitleValue(undefined); + setCurrentStatusesValue(value); }} - onBlur={() => runValidationTasks(\\"primaryTitle\\", primaryTitle)} - errorMessage={errors.primaryTitle?.errorMessage} - hasError={errors.primaryTitle?.hasError} - ref={primaryTitleRef} + onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} + errorMessage={errors.statuses?.errorMessage} + hasError={errors.statuses?.hasError} + ref={statusesRef} labelHidden={true} - {...getOverrideProps(overrides, \\"primaryTitle\\")} - > + {...getOverrideProps(overrides, \\"statuses\\")} + > + + + + + + + + + + + ); } " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 2`] = ` +exports[`amplify form renderer tests datastore 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, Title } from \\"../models\\"; +import { Post } from \\"../models\\"; 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; - primaryTitle?: Title; +export declare type TagCreateFormInputValues = { + label?: string; + Posts?: Post[]; + statuses?: string[]; }; -export declare type BookCreateFormValidationValues = { - name?: ValidationFunction; - primaryAuthor?: ValidationFunction; - primaryTitle?: ValidationFunction; +export declare type TagCreateFormValidationValues = { + label?: ValidationFunction<string>; + Posts?: ValidationFunction<Post>; + statuses?: ValidationFunction<string>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type BookCreateFormOverridesProps = { - BookCreateFormGrid?: FormProps<GridProps>; - name?: FormProps<TextFieldProps>; - primaryAuthor?: FormProps<AutocompleteProps>; - primaryTitle?: FormProps<AutocompleteProps>; +export declare type TagCreateFormOverridesProps = { + TagCreateFormGrid?: FormProps<GridProps>; + label?: FormProps<TextFieldProps>; + Posts?: FormProps<AutocompleteProps>; + statuses?: FormProps<SelectFieldProps>; } & 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 datastore form tests should generate a update form 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { + Autocomplete, + Badge, Button, + Divider, Flex, Grid, - TextAreaField, + Icon, + ScrollView, + Text, TextField, + useTheme, } from \\"@aws-amplify/ui-react\\"; -import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Post } from \\"../models\\"; +import { + getOverrideProps, + useDataStoreBinding, +} from \\"@aws-amplify/ui-react/internal\\"; +import { Book, Author, Title } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; -export default function MyPostForm(props) { - const { - id, - post, - onSuccess, - onError, - onSubmit, - onCancel, - onValidate, - onChange, - overrides, - ...rest - } = props; - const initialValues = { - TextAreaFieldbbd63464: undefined, - caption: undefined, - username: undefined, - profile_url: undefined, - post_url: undefined, - metadata: undefined, - }; - 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 ? JSON.stringify(initialValues.metadata) : undefined +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, +}) { + const { tokens } = 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 () => { + 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 = ( + <React.Fragment> + {!!items?.length && ( + <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> + {items.map((value, index) => { + return ( + <Badge + key={index} + style={{ + cursor: \\"pointer\\", + alignItems: \\"center\\", + marginRight: 3, + marginTop: 3, + backgroundColor: + index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", + }} + onClick={() => { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + <Icon + style={{ + cursor: \\"pointer\\", + paddingLeft: 3, + width: 20, + height: 20, + }} + viewBox={{ width: 20, height: 20 }} + paths={[ + { + d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", + stroke: \\"black\\", + }, + ]} + ariaLabel=\\"button\\" + onClick={(event) => { + event.stopPropagation(); + removeItem(index); + }} + /> + </Badge> + ); + })} + </ScrollView> + )} + <Divider orientation=\\"horizontal\\" marginTop={5} /> + </React.Fragment> + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + <React.Fragment> + <Text>{label}</Text> + {isEditing && children} + {!isEditing ? ( + <> + <Button + onClick={() => { + setIsEditing(true); + }} + > + Add item + </Button> + </> + ) : ( + <Flex justifyContent=\\"flex-end\\"> + {(currentFieldValue || isEditing) && ( + <Button + children=\\"Cancel\\" + type=\\"button\\" + size=\\"small\\" + onClick={() => { + setFieldValue(defaultFieldValue); + setIsEditing(false); + setSelectedBadgeIndex(undefined); + }} + ></Button> + )} + <Button + size=\\"small\\" + variation=\\"link\\" + color={tokens.colors.brand.primary[80]} + isDisabled={hasError} + onClick={addItem} + > + {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} + </Button> + </Flex> + )} + {arraySection} + </React.Fragment> + ); +} +export default function BookCreateForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + name: undefined, + primaryAuthor: undefined, + primaryTitle: undefined, + }; + const [name, setName] = React.useState(initialValues.name); + const [primaryAuthor, setPrimaryAuthor] = React.useState( + initialValues.primaryAuthor + ); + const [primaryTitle, setPrimaryTitle] = React.useState( + initialValues.primaryTitle ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - 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 - : JSON.stringify(cleanValues.metadata) - ); + setName(initialValues.name); + setPrimaryAuthor(initialValues.primaryAuthor); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setPrimaryTitle(initialValues.primaryTitle); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(\\"\\"); setErrors({}); }; - const [postRecord, setPostRecord] = React.useState(post); - React.useEffect(() => { - const queryData = async () => { - const record = id ? await DataStore.query(Post, id) : post; - setPostRecord(record); - }; - queryData(); - }, [id, post]); - React.useEffect(resetStateValues, [postRecord]); + const [ + currentPrimaryAuthorDisplayValue, + setCurrentPrimaryAuthorDisplayValue, + ] = React.useState(\\"\\"); + const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = + React.useState(undefined); + const primaryAuthorRef = React.createRef(); + const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = + React.useState(\\"\\"); + const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = + React.useState(undefined); + const primaryTitleRef = React.createRef(); + const authorRecords = useDataStoreBinding({ + type: \\"collection\\", + model: Author, + }).items; + const titleRecords = useDataStoreBinding({ + type: \\"collection\\", + model: Title, + }).items; + const getDisplayValue = { + primaryAuthor: (record) => record?.name, + primaryTitle: (record) => record?.name, + }; const validations = { - TextAreaFieldbbd63464: [], - caption: [], - username: [], - profile_url: [{ type: \\"URL\\" }], - post_url: [{ type: \\"URL\\" }], - metadata: [{ type: \\"JSON\\" }], + name: [], + primaryAuthor: [], + primaryTitle: [], }; const runValidationTasks = async ( fieldName, @@ -5406,25 +5636,30 @@ export default function MyPostForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url: profile_url || undefined, - post_url: post_url || undefined, - metadata, + 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; }, []) @@ -5436,33 +5671,31 @@ export default function MyPostForm(props) { modelFields = onSubmit(modelFields); } try { - const original = await DataStore.query(Post, id); - await DataStore.save( - Post.copyOf(original, (updated) => { - Object.assign(updated, modelFields); - }) - ); + await DataStore.save(new Book(modelFields)); if (onSuccess) { onSuccess(modelFields); } - } catch (err) { - if (onError) { + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { onError(modelFields, err.message); } } }} {...rest} - {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...getOverrideProps(overrides, \\"BookCreateForm\\")} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button - children=\\"Reset\\" + children=\\"Clear\\" type=\\"reset\\" onClick={resetStateValues} - {...getOverrideProps(overrides, \\"ResetButton\\")} + {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> <Button @@ -5482,434 +5715,223 @@ export default function MyPostForm(props) { ></Button> </Flex> </Flex> - <TextAreaField - label=\\"Label\\" - defaultValue={TextAreaFieldbbd63464} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464: value, - caption, - username, - profile_url, - post_url, - metadata, - }; - 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} - defaultValue={caption} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption: value, - username, - profile_url, - post_url, - metadata, - }; - 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} - defaultValue={username} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption, - username: value, - profile_url, - post_url, - metadata, - }; - 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\\" + label=\\"Name\\" isRequired={false} isReadOnly={false} - defaultValue={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url: value, - post_url, - metadata, + name: value, + primaryAuthor, + primaryTitle, }; const result = onChange(modelFields); - value = result?.profile_url ?? value; + value = result?.name ?? value; } - if (errors.profile_url?.hasError) { - runValidationTasks(\\"profile_url\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setProfile_url(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} - errorMessage={errors.profile_url?.errorMessage} - hasError={errors.profile_url?.hasError} - {...getOverrideProps(overrides, \\"profile_url\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} ></TextField> - <TextField - label=\\"Post url\\" - isRequired={false} - isReadOnly={false} - defaultValue={post_url} - onChange={(e) => { - let { value } = e.target; + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url: value, - metadata, + name, + primaryAuthor: value, + primaryTitle, }; const result = onChange(modelFields); - value = result?.post_url ?? value; - } - if (errors.post_url?.hasError) { - runValidationTasks(\\"post_url\\", value); + value = result?.primaryAuthor ?? value; } - setPost_url(value); + setPrimaryAuthor(value); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} - 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} - defaultValue={metadata} - onChange={(e) => { - let { value } = e.target; + currentFieldValue={currentPrimaryAuthorValue} + label={\\"Primary author\\"} + items={primaryAuthor ? [primaryAuthor] : []} + hasError={errors.primaryAuthor?.hasError} + getBadgeText={getDisplayValue.primaryAuthor} + setFieldValue={(model) => + setCurrentPrimaryAuthorDisplayValue( + getDisplayValue.primaryAuthor(model) + ) + } + inputFieldRef={primaryAuthorRef} + defaultFieldValue={\\"\\"} + > + <Autocomplete + label=\\"Primary author\\" + isRequired={false} + isReadOnly={false} + value={currentPrimaryAuthorDisplayValue} + options={authorRecords.map((r) => ({ + id: r.id, + label: getDisplayValue.primaryAuthor?.(r) ?? r.id, + }))} + onSelect={({ id, label }) => { + setCurrentPrimaryAuthorValue( + authorRecords.find((r) => r.id === id) + ); + setCurrentPrimaryAuthorDisplayValue(label); + }} + onClear={() => { + setCurrentPrimaryAuthorDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + if (errors.primaryAuthor?.hasError) { + runValidationTasks(\\"primaryAuthor\\", value); + } + setCurrentPrimaryAuthorDisplayValue(value); + setCurrentPrimaryAuthorValue(undefined); + }} + onBlur={() => runValidationTasks(\\"primaryAuthor\\", primaryAuthor)} + errorMessage={errors.primaryAuthor?.errorMessage} + hasError={errors.primaryAuthor?.hasError} + ref={primaryAuthorRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"primaryAuthor\\")} + ></Autocomplete> + </ArrayField> + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata: value, + name, + primaryAuthor, + primaryTitle: value, }; const result = onChange(modelFields); - value = result?.metadata ?? value; - } - if (errors.metadata?.hasError) { - runValidationTasks(\\"metadata\\", value); + value = result?.primaryTitle ?? value; } - setMetadata(value); + setPrimaryTitle(value); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(\\"\\"); }} - onBlur={() => runValidationTasks(\\"metadata\\", metadata)} - errorMessage={errors.metadata?.errorMessage} - hasError={errors.metadata?.hasError} - {...getOverrideProps(overrides, \\"metadata\\")} - ></TextAreaField> - <Flex - justifyContent=\\"space-between\\" - {...getOverrideProps(overrides, \\"CTAFlex\\")} + currentFieldValue={currentPrimaryTitleValue} + label={\\"Primary title\\"} + items={primaryTitle ? [primaryTitle] : []} + hasError={errors.primaryTitle?.hasError} + getBadgeText={getDisplayValue.primaryTitle} + setFieldValue={(model) => + setCurrentPrimaryTitleDisplayValue( + getDisplayValue.primaryTitle(model) + ) + } + inputFieldRef={primaryTitleRef} + defaultFieldValue={\\"\\"} > - <Button - children=\\"Reset\\" - type=\\"reset\\" - onClick={resetStateValues} - {...getOverrideProps(overrides, \\"ResetButton\\")} - ></Button> - <Flex {...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> + <Autocomplete + label=\\"Primary title\\" + isRequired={false} + isReadOnly={false} + value={currentPrimaryTitleDisplayValue} + options={titleRecords.map((r) => ({ + id: r.id, + label: getDisplayValue.primaryTitle?.(r) ?? r.id, + }))} + onSelect={({ id, label }) => { + setCurrentPrimaryTitleValue(titleRecords.find((r) => r.id === id)); + setCurrentPrimaryTitleDisplayValue(label); + }} + onClear={() => { + setCurrentPrimaryTitleDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + if (errors.primaryTitle?.hasError) { + runValidationTasks(\\"primaryTitle\\", value); + } + setCurrentPrimaryTitleDisplayValue(value); + setCurrentPrimaryTitleValue(undefined); + }} + onBlur={() => runValidationTasks(\\"primaryTitle\\", primaryTitle)} + errorMessage={errors.primaryTitle?.errorMessage} + hasError={errors.primaryTitle?.hasError} + ref={primaryTitleRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"primaryTitle\\")} + ></Autocomplete> + </ArrayField> </Grid> ); } " `; -exports[`amplify form renderer tests datastore form tests should generate a update form 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 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 \\"../models\\"; +import { Author, Title } from \\"../models\\"; 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; +export declare type BookCreateFormInputValues = { + name?: string; + primaryAuthor?: Author; + primaryTitle?: Title; }; -export declare type MyPostFormValidationValues = { - TextAreaFieldbbd63464?: ValidationFunction<string>; - caption?: ValidationFunction<string>; - username?: ValidationFunction<string>; - profile_url?: ValidationFunction<string>; - post_url?: ValidationFunction<string>; - metadata?: ValidationFunction<string>; +export declare type BookCreateFormValidationValues = { + name?: ValidationFunction<string>; + primaryAuthor?: ValidationFunction<Author>; + primaryTitle?: ValidationFunction<Title>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type MyPostFormOverridesProps = { - MyPostFormGrid?: FormProps<GridProps>; - TextAreaFieldbbd63464?: FormProps<TextAreaFieldProps>; - caption?: FormProps<TextFieldProps>; - username?: FormProps<TextFieldProps>; - profile_url?: FormProps<TextFieldProps>; - post_url?: FormProps<TextFieldProps>; - metadata?: FormProps<TextAreaFieldProps>; +export declare type BookCreateFormOverridesProps = { + BookCreateFormGrid?: FormProps<GridProps>; + name?: FormProps<TextFieldProps>; + primaryAuthor?: FormProps<AutocompleteProps>; + primaryTitle?: FormProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type MyPostFormProps = React.PropsWithChildren<{ - overrides?: MyPostFormOverridesProps | undefined | null; +export declare type BookCreateFormProps = React.PropsWithChildren<{ + overrides?: BookCreateFormOverridesProps | undefined | null; } & { - id?: string; - post?: Post; - onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onSuccess?: (fields: MyPostFormInputValues) => void; - onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + clearOnSuccess?: boolean; + onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onSuccess?: (fields: BookCreateFormInputValues) => void; + onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onValidate?: MyPostFormValidationValues; + onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; -export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should generate an update form with belongsTo relationship 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a update form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { - Autocomplete, - Badge, Button, - Divider, Flex, Grid, - Icon, - ScrollView, - Text, + TextAreaField, TextField, - useTheme, } from \\"@aws-amplify/ui-react\\"; -import { - getOverrideProps, - useDataStoreBinding, -} from \\"@aws-amplify/ui-react/internal\\"; -import { Member, Team as Team0 } from \\"../models\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Post } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; -function ArrayField({ - items = [], - onChange, - label, - inputFieldRef, - children, - hasError, - setFieldValue, - currentFieldValue, - defaultFieldValue, - lengthLimit, - getBadgeText, -}) { - const { tokens } = 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 () => { - 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 = ( - <React.Fragment> - {!!items?.length && ( - <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> - {items.map((value, index) => { - return ( - <Badge - key={index} - style={{ - cursor: \\"pointer\\", - alignItems: \\"center\\", - marginRight: 3, - marginTop: 3, - backgroundColor: - index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", - }} - onClick={() => { - setSelectedBadgeIndex(index); - setFieldValue(items[index]); - setIsEditing(true); - }} - > - {getBadgeText ? getBadgeText(value) : value.toString()} - <Icon - style={{ - cursor: \\"pointer\\", - paddingLeft: 3, - width: 20, - height: 20, - }} - viewBox={{ width: 20, height: 20 }} - paths={[ - { - d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", - stroke: \\"black\\", - }, - ]} - ariaLabel=\\"button\\" - onClick={(event) => { - event.stopPropagation(); - removeItem(index); - }} - /> - </Badge> - ); - })} - </ScrollView> - )} - <Divider orientation=\\"horizontal\\" marginTop={5} /> - </React.Fragment> - ); - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( - <React.Fragment> - <Text>{label}</Text> - {isEditing && children} - {!isEditing ? ( - <> - <Button - onClick={() => { - setIsEditing(true); - }} - > - Add item - </Button> - </> - ) : ( - <Flex justifyContent=\\"flex-end\\"> - {(currentFieldValue || isEditing) && ( - <Button - children=\\"Cancel\\" - type=\\"button\\" - size=\\"small\\" - onClick={() => { - setFieldValue(defaultFieldValue); - setIsEditing(false); - setSelectedBadgeIndex(undefined); - }} - ></Button> - )} - <Button - size=\\"small\\" - variation=\\"link\\" - color={tokens.colors.brand.primary[80]} - isDisabled={hasError} - onClick={addItem} - > - {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} - </Button> - </Flex> - )} - {arraySection} - </React.Fragment> - ); -} -export default function MyMemberForm(props) { +export default function MyPostForm(props) { const { id, - member, + post, onSuccess, onError, onSubmit, @@ -5920,47 +5942,58 @@ export default function MyMemberForm(props) { ...rest } = props; const initialValues = { - name: undefined, - Team: undefined, + TextAreaFieldbbd63464: undefined, + caption: undefined, + username: undefined, + profile_url: undefined, + post_url: undefined, + metadata: undefined, }; - const [name, setName] = React.useState(initialValues.name); - const [Team, setTeam] = React.useState(initialValues.Team); + 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 ? JSON.stringify(initialValues.metadata) : undefined + ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - const cleanValues = memberRecord - ? { ...initialValues, ...memberRecord, Team } + const cleanValues = postRecord + ? { ...initialValues, ...postRecord } : initialValues; - setName(cleanValues.name); - setTeam(cleanValues.Team); - setCurrentTeamValue(undefined); - setCurrentTeamDisplayValue(\\"\\"); + 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 + : JSON.stringify(cleanValues.metadata) + ); setErrors({}); }; - const [memberRecord, setMemberRecord] = React.useState(member); + const [postRecord, setPostRecord] = React.useState(post); React.useEffect(() => { const queryData = async () => { - const record = id ? await DataStore.query(Member, id) : member; - setMemberRecord(record); - const TeamRecord = record ? await record.Team : undefined; - setTeam(TeamRecord); + const record = id ? await DataStore.query(Post, id) : post; + setPostRecord(record); }; queryData(); - }, [id, member]); - React.useEffect(resetStateValues, [memberRecord, Team]); - const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = - React.useState(\\"\\"); - const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); - const TeamRef = React.createRef(); - const teamRecords = useDataStoreBinding({ - type: \\"collection\\", - model: Team0, - }).items; - const getDisplayValue = { - Team: (record) => record?.name, - }; + }, [id, post]); + React.useEffect(resetStateValues, [postRecord]); const validations = { - name: [], - Team: [], + TextAreaFieldbbd63464: [], + caption: [], + username: [], + profile_url: [{ type: \\"URL\\" }], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, @@ -5987,29 +6020,25 @@ export default function MyMemberForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - name, - Team, + TextAreaFieldbbd63464, + caption, + username, + profile_url: profile_url || undefined, + post_url: post_url || undefined, + metadata, }; 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; }, []) @@ -6021,24 +6050,226 @@ export default function MyMemberForm(props) { modelFields = onSubmit(modelFields); } try { - const original = await DataStore.query(Member, id); + const original = await DataStore.query(Post, id); await DataStore.save( - Member.copyOf(original, (updated) => { + Post.copyOf(original, (updated) => { Object.assign(updated, modelFields); }) ); if (onSuccess) { onSuccess(modelFields); } - } catch (err) { - if (onError) { - onError(modelFields, err.message); + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...rest} + {...getOverrideProps(overrides, \\"MyPostForm\\")} + > + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={resetStateValues} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex {...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> + <TextAreaField + label=\\"Label\\" + defaultValue={TextAreaFieldbbd63464} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464: value, + caption, + username, + profile_url, + post_url, + metadata, + }; + 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} + defaultValue={caption} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption: value, + username, + profile_url, + post_url, + metadata, + }; + const result = onChange(modelFields); + value = result?.caption ?? value; + } + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); } - } - }} - {...rest} - {...getOverrideProps(overrides, \\"MyMemberForm\\")} - > + 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} + defaultValue={username} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username: value, + profile_url, + post_url, + metadata, + }; + 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} + defaultValue={profile_url} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url: value, + post_url, + metadata, + }; + 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} + defaultValue={post_url} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url: value, + metadata, + }; + 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} + defaultValue={metadata} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata: value, + }; + 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\\")} + ></TextAreaField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} @@ -6067,138 +6298,65 @@ export default function MyMemberForm(props) { ></Button> </Flex> </Flex> - <TextField - label=\\"Name\\" - isRequired={false} - isReadOnly={false} - defaultValue={name} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - name: value, - 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\\")} - ></TextField> - <ArrayField - lengthLimit={1} - onChange={async (items) => { - let value = items[0]; - if (onChange) { - const modelFields = { - name, - 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} - getBadgeText={getDisplayValue.Team} - setFieldValue={(model) => - setCurrentTeamDisplayValue(getDisplayValue.Team(model)) - } - inputFieldRef={TeamRef} - defaultFieldValue={\\"\\"} - > - <Autocomplete - label=\\"Team Label\\" - isRequired={false} - isReadOnly={false} - value={currentTeamDisplayValue} - options={teamRecords.map((r) => ({ - id: r.id, - label: getDisplayValue.Team?.(r) ?? r.id, - }))} - onSelect={({ id, label }) => { - setCurrentTeamValue(teamRecords.find((r) => r.id === id)); - setCurrentTeamDisplayValue(label); - }} - onClear={() => { - setCurrentTeamDisplayValue(\\"\\"); - }} - defaultValue={Team} - onChange={(e) => { - let { value } = e.target; - if (errors.Team?.hasError) { - runValidationTasks(\\"Team\\", value); - } - setCurrentTeamDisplayValue(value); - setCurrentTeamValue(undefined); - }} - onBlur={() => runValidationTasks(\\"Team\\", Team)} - errorMessage={errors.Team?.errorMessage} - hasError={errors.Team?.hasError} - ref={TeamRef} - labelHidden={true} - {...getOverrideProps(overrides, \\"Team\\")} - ></Autocomplete> - </ArrayField> </Grid> ); } " `; -exports[`amplify form renderer tests datastore form tests should generate an update form with belongsTo relationship 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate a update form 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 { Member, Team as Team0 } from \\"../models\\"; +import { Post } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type MyMemberFormInputValues = { - name?: string; - Team?: Team0; +export declare type MyPostFormInputValues = { + TextAreaFieldbbd63464?: string; + caption?: string; + username?: string; + profile_url?: string; + post_url?: string; + metadata?: string; }; -export declare type MyMemberFormValidationValues = { - name?: ValidationFunction<string>; - Team?: ValidationFunction<Team0>; +export declare type MyPostFormValidationValues = { + TextAreaFieldbbd63464?: ValidationFunction<string>; + caption?: ValidationFunction<string>; + username?: ValidationFunction<string>; + profile_url?: ValidationFunction<string>; + post_url?: ValidationFunction<string>; + metadata?: ValidationFunction<string>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type MyMemberFormOverridesProps = { - MyMemberFormGrid?: FormProps<GridProps>; - name?: FormProps<TextFieldProps>; - Team?: FormProps<AutocompleteProps>; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: FormProps<GridProps>; + TextAreaFieldbbd63464?: FormProps<TextAreaFieldProps>; + caption?: FormProps<TextFieldProps>; + username?: FormProps<TextFieldProps>; + profile_url?: FormProps<TextFieldProps>; + post_url?: FormProps<TextFieldProps>; + metadata?: FormProps<TextAreaFieldProps>; } & EscapeHatchProps; -export declare type MyMemberFormProps = React.PropsWithChildren<{ - overrides?: MyMemberFormOverridesProps | undefined | null; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; } & { id?: string; - member?: Member; - onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onSuccess?: (fields: MyMemberFormInputValues) => void; - onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; + post?: Post; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onValidate?: MyMemberFormValidationValues; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; -export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should generate an update form with manyToMany relationship 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate an update form with belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -6218,7 +6376,7 @@ import { getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; -import { Tag, Post, TagPost } from \\"../models\\"; +import { Member, Team as Team0 } from \\"../models\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { DataStore } from \\"aws-amplify\\"; function ArrayField({ @@ -6362,10 +6520,10 @@ function ArrayField({ </React.Fragment> ); } -export default function TagUpdateForm(props) { +export default function MyMemberForm(props) { const { id, - tag, + member, onSuccess, onError, onSubmit, @@ -6376,56 +6534,47 @@ export default function TagUpdateForm(props) { ...rest } = props; const initialValues = { - label: undefined, - Posts: [], + name: undefined, + Team: undefined, }; - const [label, setLabel] = React.useState(initialValues.label); - const [Posts, setPosts] = React.useState(initialValues.Posts); + const [name, setName] = React.useState(initialValues.name); + const [Team, setTeam] = React.useState(initialValues.Team); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - const cleanValues = tagRecord - ? { ...initialValues, ...tagRecord, Posts: linkedPosts } + const cleanValues = memberRecord + ? { ...initialValues, ...memberRecord, Team } : initialValues; - setLabel(cleanValues.label); - setPosts(cleanValues.Posts ?? []); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); + setName(cleanValues.name); + setTeam(cleanValues.Team); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(\\"\\"); setErrors({}); }; - const [tagRecord, setTagRecord] = React.useState(tag); - const [linkedPosts, setLinkedPosts] = React.useState([]); + const [memberRecord, setMemberRecord] = React.useState(member); React.useEffect(() => { const queryData = async () => { - const record = id ? await DataStore.query(Tag, id) : tag; - const linkedPosts = record - ? await Promise.all( - ( - await record.Posts.toArray() - ).map((r) => { - return r.post; - }) - ) - : []; - setLinkedPosts(linkedPosts); - setTagRecord(record); + const record = id ? await DataStore.query(Member, id) : member; + setMemberRecord(record); + const TeamRecord = record ? await record.Team : undefined; + setTeam(TeamRecord); }; queryData(); - }, [id, tag]); - React.useEffect(resetStateValues, [tagRecord, linkedPosts]); - const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = + }, [id, member]); + React.useEffect(resetStateValues, [memberRecord, Team]); + const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = React.useState(\\"\\"); - const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); - const PostsRef = React.createRef(); - const postRecords = useDataStoreBinding({ + const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); + const TeamRef = React.createRef(); + const teamRecords = useDataStoreBinding({ type: \\"collection\\", - model: Post, + model: Team0, }).items; const getDisplayValue = { - Posts: (record) => record?.title, + Team: (record) => record?.name, }; const validations = { - label: [], - Posts: [], + name: [], + Team: [], }; const runValidationTasks = async ( fieldName, @@ -6452,8 +6601,8 @@ export default function TagUpdateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - label, - Posts, + name, + Team, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -6486,71 +6635,12 @@ export default function TagUpdateForm(props) { modelFields = onSubmit(modelFields); } try { - const postsToLinkMap = new Map(); - const postsToUnLinkMap = new Map(); - const postsMap = new Map(); - const linkedPostsMap = new Map(); - Posts.forEach((r) => { - const count = postsMap.get(r.id); - const newCount = count ? count + 1 : 1; - postsMap.set(r.id, newCount); - }); - linkedPosts.forEach((r) => { - const count = linkedPostsMap.get(r.id); - const newCount = count ? count + 1 : 1; - linkedPostsMap.set(r.id, newCount); - }); - linkedPostsMap.forEach((count, id) => { - const newCount = postsMap.get(id); - if (newCount) { - const diffCount = count - newCount; - if (diffCount > 0) { - postsToUnLinkMap.set(id, diffCount); - } - } else { - postsToUnLinkMap.set(id, count); - } - }); - postsMap.forEach((count, id) => { - const originalCount = linkedPostsMap.get(id); - if (originalCount) { - const diffCount = count - originalCount; - if (diffCount > 0) { - postsToLinkMap.set(id, diffCount); - } - } else { - postsToLinkMap.set(id, count); - } - }); - const promises = []; - postsToUnLinkMap.forEach(async (count, id) => { - const tagPostRecords = await DataStore.query(TagPost, (r) => - r.and((r) => [r.post.id.eq(id), r.tag.id.eq(tagRecord.id)]) - ); - for (let i = 0; i < count; i++) { - promises.push(DataStore.delete(tagPostRecords[i])); - } - }); - postsToLinkMap.forEach((count, id) => { - for (let i = count; i > 0; i--) { - promises.push( - DataStore.save( - new TagPost({ - tagID: tagRecord.id, - postID: id, - }) - ) - ); - } - }); - promises.push( - DataStore.save( - Tag.copyOf(tagRecord, (updated) => { - Object.assign(updated, modelFields); - }) - ) + const original = await DataStore.query(Member, id); + await DataStore.save( + Member.copyOf(original, (updated) => { + Object.assign(updated, modelFields); + }) ); - await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -6561,162 +6651,164 @@ export default function TagUpdateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"TagUpdateForm\\")} + {...getOverrideProps(overrides, \\"MyMemberForm\\")} > + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={resetStateValues} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex {...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=\\"Label\\" + label=\\"Name\\" isRequired={false} isReadOnly={false} - defaultValue={label} + defaultValue={name} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - label: value, - Posts, + name: value, + Team, }; const result = onChange(modelFields); - value = result?.label ?? value; + value = result?.name ?? value; } - if (errors.label?.hasError) { - runValidationTasks(\\"label\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setLabel(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"label\\", label)} - errorMessage={errors.label?.errorMessage} - hasError={errors.label?.hasError} - {...getOverrideProps(overrides, \\"label\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} ></TextField> <ArrayField + lengthLimit={1} onChange={async (items) => { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { - label, - Posts: values, + name, + Team: value, }; const result = onChange(modelFields); - values = result?.Posts ?? values; + value = result?.Team ?? value; } - setPosts(values); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); + setTeam(value); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(\\"\\"); }} - currentFieldValue={currentPostsValue} - label={\\"Posts\\"} - items={Posts} - hasError={errors.Posts?.hasError} - getBadgeText={getDisplayValue.Posts} + currentFieldValue={currentTeamValue} + label={\\"Team Label\\"} + items={Team ? [Team] : []} + hasError={errors.Team?.hasError} + getBadgeText={getDisplayValue.Team} setFieldValue={(model) => - setCurrentPostsDisplayValue(getDisplayValue.Posts(model)) + setCurrentTeamDisplayValue(getDisplayValue.Team(model)) } - inputFieldRef={PostsRef} + inputFieldRef={TeamRef} defaultFieldValue={\\"\\"} > <Autocomplete - label=\\"Posts\\" + label=\\"Team Label\\" isRequired={false} isReadOnly={false} - value={currentPostsDisplayValue} - options={postRecords.map((r) => ({ + value={currentTeamDisplayValue} + options={teamRecords.map((r) => ({ id: r.id, - label: getDisplayValue.Posts?.(r) ?? r.id, + label: getDisplayValue.Team?.(r) ?? r.id, }))} onSelect={({ id, label }) => { - setCurrentPostsValue(postRecords.find((r) => r.id === id)); - setCurrentPostsDisplayValue(label); + setCurrentTeamValue(teamRecords.find((r) => r.id === id)); + setCurrentTeamDisplayValue(label); }} onClear={() => { - setCurrentPostsDisplayValue(\\"\\"); + setCurrentTeamDisplayValue(\\"\\"); }} + defaultValue={Team} onChange={(e) => { let { value } = e.target; - if (errors.Posts?.hasError) { - runValidationTasks(\\"Posts\\", value); + if (errors.Team?.hasError) { + runValidationTasks(\\"Team\\", value); } - setCurrentPostsDisplayValue(value); - setCurrentPostsValue(undefined); + setCurrentTeamDisplayValue(value); + setCurrentTeamValue(undefined); }} - onBlur={() => runValidationTasks(\\"Posts\\", currentPostsValue)} - errorMessage={errors.Posts?.errorMessage} - hasError={errors.Posts?.hasError} - ref={PostsRef} + onBlur={() => runValidationTasks(\\"Team\\", Team)} + errorMessage={errors.Team?.errorMessage} + hasError={errors.Team?.hasError} + ref={TeamRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Posts\\")} + {...getOverrideProps(overrides, \\"Team\\")} ></Autocomplete> </ArrayField> - <Flex - justifyContent=\\"space-between\\" - {...getOverrideProps(overrides, \\"CTAFlex\\")} - > - <Button - children=\\"Reset\\" - type=\\"reset\\" - onClick={resetStateValues} - {...getOverrideProps(overrides, \\"ResetButton\\")} - ></Button> - <Flex {...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> </Grid> ); } " `; -exports[`amplify form renderer tests datastore form tests should generate an update form with manyToMany relationship 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate an update 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 { Tag, Post } from \\"../models\\"; +import { Member, Team as Team0 } from \\"../models\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type TagUpdateFormInputValues = { - label?: string; - Posts?: Post[]; +export declare type MyMemberFormInputValues = { + name?: string; + Team?: Team0; }; -export declare type TagUpdateFormValidationValues = { - label?: ValidationFunction<string>; - Posts?: ValidationFunction<Post>; +export declare type MyMemberFormValidationValues = { + name?: ValidationFunction<string>; + Team?: ValidationFunction<Team0>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type TagUpdateFormOverridesProps = { - TagUpdateFormGrid?: FormProps<GridProps>; - label?: FormProps<TextFieldProps>; - Posts?: FormProps<AutocompleteProps>; +export declare type MyMemberFormOverridesProps = { + MyMemberFormGrid?: FormProps<GridProps>; + name?: FormProps<TextFieldProps>; + Team?: FormProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type TagUpdateFormProps = React.PropsWithChildren<{ - overrides?: TagUpdateFormOverridesProps | undefined | null; +export declare type MyMemberFormProps = React.PropsWithChildren<{ + overrides?: MyMemberFormOverridesProps | undefined | null; } & { id?: string; - tag?: Tag; - onSubmit?: (fields: TagUpdateFormInputValues) => TagUpdateFormInputValues; - onSuccess?: (fields: TagUpdateFormInputValues) => void; - onError?: (fields: TagUpdateFormInputValues, errorMessage: string) => void; + member?: Member; + onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onSuccess?: (fields: MyMemberFormInputValues) => void; + onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: TagUpdateFormInputValues) => TagUpdateFormInputValues; - onValidate?: TagUpdateFormValidationValues; + onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onValidate?: MyMemberFormValidationValues; } & React.CSSProperties>; -export default function TagUpdateForm(props: TagUpdateFormProps): React.ReactElement; +export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; 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 ccb0730a0..c5a40281f 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 @@ -121,10 +121,7 @@ describe('amplify form renderer tests', () => { }); it('should generate an update form with manyToMany relationship', () => { - const { componentText, declaration } = generateWithAmplifyFormRenderer( - 'forms/tag-datastore-update', - 'datastore/tag-post', - ); + const { componentText } = generateWithAmplifyFormRenderer('forms/tag-datastore-update', 'datastore/tag-post'); // check nested model is imported expect(componentText).toContain('import { Tag, Post, TagPost } from "../models";'); @@ -142,7 +139,22 @@ describe('amplify form renderer tests', () => { // check resetStateValues has correct dependencies expect(componentText).toContain('React.useEffect(resetStateValues, [tagRecord, linkedPosts]);'); + }); + it('should generate a create form with array of Enums', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/tag-datastore-create', + 'datastore/tag-post', + ); + // get displayValue function + expect(componentText).toContain('statuses: (record) => {'); + expect(componentText).toContain('return enumDisplayValueMap[record];'); + // ArrayField returns the item on a badge click + expect(componentText).toContain('setFieldValue(items[index]);'); + // set the badgeText param + expect(componentText).toContain('getBadgeText={getDisplayValue.statuses}'); + // ArrayField displays the getBadgeText return value + expect(componentText).toContain('{getBadgeText ? getBadgeText(value) : value.toString()}'); expect(componentText).toMatchSnapshot(); expect(declaration).toMatchSnapshot(); }); diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts index cf42dd970..30b1f9a2a 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts @@ -20,6 +20,7 @@ import { StudioFormValueMappings, } from '@aws-amplify/codegen-ui'; import { StudioFormInputFieldProperty } from '@aws-amplify/codegen-ui/lib/types/form/input-config'; +import { isEnumFieldType } from '@aws-amplify/datastore'; import { Expression, factory, @@ -28,12 +29,14 @@ import { SyntaxKind, NodeFlags, CallExpression, + VariableStatement, } from 'typescript'; import { buildBindingExpression, buildConcatExpression, isBoundProperty, isConcatenatedProperty, + isFixedPropertyWithValue, } from '../../react-component-render-helper'; import { getRecordsName } from './form-state'; import { getElementAccessExpression } from './invalid-variable-helpers'; @@ -213,6 +216,10 @@ export function getDisplayValueObject(displayValueFunctions: PropertyAssignment[ // example - primaryAuthor: (record) => record?.name, export function buildDisplayValueFunction(fieldName: string, fieldConfig: FieldConfigMetadata): PropertyAssignment { const recordString = 'record'; + const propertyName = isValidVariableName(fieldName) + ? factory.createIdentifier(fieldName) + : factory.createStringLiteral(fieldName); + let additionalStatements: VariableStatement[] = []; let renderedDisplayValue: Expression = factory.createPropertyAccessChain( factory.createIdentifier(recordString), factory.createToken(SyntaxKind.QuestionDotToken), @@ -233,8 +240,52 @@ export function buildDisplayValueFunction(fieldName: string, fieldConfig: FieldC } } + if (isEnumFieldType(fieldConfig.dataType) && fieldConfig.valueMappings && fieldConfig.isArray) { + const displayValueMapName = `enumDisplayValueMap`; + additionalStatements = [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier(displayValueMapName), + undefined, + undefined, + factory.createObjectLiteralExpression( + fieldConfig.valueMappings.values.map((v) => { + let value = ''; + let displayValue = ''; + if (isFixedPropertyWithValue(v.value)) { + value = v.value.value.toString(); + } + if (v.displayValue && isFixedPropertyWithValue(v.displayValue)) { + displayValue = v.displayValue.value.toString(); + } + if (value === '') { + throw Error('Enum cannot have an empty value'); + } + return factory.createPropertyAssignment( + factory.createStringLiteral(value), + factory.createStringLiteral(displayValue ?? value), + ); + }), + + true, + ), + ), + ], + NodeFlags.Const, + ), + ), + ]; + renderedDisplayValue = factory.createElementAccessExpression( + factory.createIdentifier(displayValueMapName), + factory.createIdentifier(recordString), + ); + } + return factory.createPropertyAssignment( - isValidVariableName(fieldName) ? factory.createIdentifier(fieldName) : factory.createStringLiteral(fieldName), + propertyName, factory.createArrowFunction( undefined, undefined, @@ -251,7 +302,9 @@ export function buildDisplayValueFunction(fieldName: string, fieldConfig: FieldC ], undefined, factory.createToken(SyntaxKind.EqualsGreaterThanToken), - renderedDisplayValue, + additionalStatements.length + ? factory.createBlock([...additionalStatements, factory.createReturnStatement(renderedDisplayValue)], false) + : renderedDisplayValue, ), ); } diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/map-from-fieldConfigs.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/map-from-fieldConfigs.ts index 9f4b02bd5..7b2c99e0d 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/map-from-fieldConfigs.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/map-from-fieldConfigs.ts @@ -16,7 +16,7 @@ import { FieldConfigMetadata } from '@aws-amplify/codegen-ui'; import { PropertyAssignment } from 'typescript'; import { buildDisplayValueFunction, getDisplayValueObject, getModelsToImport } from './display-value'; -import { isModelDataType, shouldWrapInArrayField } from './render-checkers'; +import { shouldImplementDisplayValueFunction, shouldWrapInArrayField } from './render-checkers'; import { buildValidationForField, buildValidations } from './validation'; /** @@ -54,7 +54,7 @@ export function mapFromFieldConfigs(fieldConfigs: Record<string, FieldConfigMeta } // displayValue - if (isModelDataType(fieldConfig)) { + if (shouldImplementDisplayValueFunction(fieldConfig)) { displayValueFunctions.push(buildDisplayValueFunction(fieldName, fieldConfig)); } diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts index 545d3986b..920088a92 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts @@ -25,7 +25,7 @@ import { setFieldState, } from './form-state'; import { buildOverrideOnChangeStatement } from './event-handler-props'; -import { isModelDataType } from './render-checkers'; +import { isModelDataType, shouldImplementDisplayValueFunction } from './render-checkers'; import { getDisplayValueObjectName } from './display-value'; import { getElementAccessExpression } from './invalid-variable-helpers'; @@ -211,7 +211,7 @@ export const renderArrayFieldComponent = ( let setFieldValueIdentifier = setStateName; - if (isModelDataType(fieldConfig)) { + if (shouldImplementDisplayValueFunction(fieldConfig)) { setFieldValueIdentifier = getSetNameIdentifier(getCurrentDisplayValueName(renderedFieldName)); props.push( factory.createJsxAttribute( diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-checkers.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-checkers.ts index da2919572..68f4bb398 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-checkers.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-checkers.ts @@ -14,10 +14,15 @@ limitations under the License. */ import { FieldConfigMetadata } from '@aws-amplify/codegen-ui'; +import { isEnumFieldType } from '@aws-amplify/datastore'; -export const shouldWrapInArrayField = (config: FieldConfigMetadata) => config.isArray || config.relationship; +export const shouldWrapInArrayField = (config: FieldConfigMetadata): boolean => config.isArray || !!config.relationship; export const isModelDataType = ( config: FieldConfigMetadata, ): config is FieldConfigMetadata & { dataType: { model: string } } => !!(config.dataType && typeof config.dataType === 'object' && 'model' in config.dataType); + +export const shouldImplementDisplayValueFunction = (config: FieldConfigMetadata): boolean => { + return isModelDataType(config) || (isEnumFieldType(config.dataType) && shouldWrapInArrayField(config)); +}; diff --git a/packages/codegen-ui/example-schemas/datastore/tag-post.json b/packages/codegen-ui/example-schemas/datastore/tag-post.json index 78c08a227..1440ef82e 100644 --- a/packages/codegen-ui/example-schemas/datastore/tag-post.json +++ b/packages/codegen-ui/example-schemas/datastore/tag-post.json @@ -31,6 +31,15 @@ "associatedWith": "tag" } }, + "statuses": { + "name": "statuses", + "isArray": true, + "type": { + "enum": "Status" + }, + "isRequired": false, + "attributes": [] + }, "createdAt": { "name": "createdAt", "isArray": false, @@ -220,7 +229,16 @@ ] } }, - "enums": {}, + "enums": { + "Status": { + "name": "Status", + "values": [ + "PENDING", + "POSTED", + "IN_REVIEW" + ] + } + }, "nonModels": {}, "codegenVersion": "3.3.1", "version": "6661fbcb644d38cea3d27e2933e70457" diff --git a/packages/codegen-ui/lib/utils/form-component-metadata.ts b/packages/codegen-ui/lib/utils/form-component-metadata.ts index 8c9d2114d..4bc24baa8 100644 --- a/packages/codegen-ui/lib/utils/form-component-metadata.ts +++ b/packages/codegen-ui/lib/utils/form-component-metadata.ts @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ - +import { isEnumFieldType } from '@aws-amplify/datastore'; import { camelCase } from 'change-case'; import { @@ -92,7 +92,7 @@ export const mapFormMetadata = (form: StudioForm, formDefinition: FormDefinition }); } - if (element.relationship && 'valueMappings' in element) { + if ((element.relationship || isEnumFieldType(element.dataType)) && 'valueMappings' in element) { metadata.valueMappings = element.valueMappings; } diff --git a/packages/codegen-ui/package-lock.json b/packages/codegen-ui/package-lock.json index ac60cfa19..ec645bf24 100644 --- a/packages/codegen-ui/package-lock.json +++ b/packages/codegen-ui/package-lock.json @@ -12492,7 +12492,7 @@ "open": "^6.2.0", "ora": "^5.4.1", "semver": "^6.3.0", - "shell-quote": ">=1.7.3" + "shell-quote": "^1.7.3" }, "dependencies": { "find-up": { @@ -16238,7 +16238,7 @@ "dev": true, "peer": true, "requires": { - "shell-quote": ">=1.7.3", + "shell-quote": "^1.6.1", "ws": "^7" }, "dependencies": {