From 6a60c98de358dd907aad11de33e73321e160e371 Mon Sep 17 00:00:00 2001 From: Charles Shin Date: Fri, 26 May 2023 10:34:23 -0700 Subject: [PATCH] feat: add graphql support for update form (#1015) * feat: add graphql support for update form * fix: update snapshot * feat: add more assertions to check for graphql support * fix: removing obselete snapshot * fix: force update mutation to use id var --- ...studio-ui-codegen-react-forms.test.ts.snap | 790 ++++++++++++++++++ .../studio-ui-codegen-react-forms.test.ts | 23 + .../forms/form-renderer-helper/cta-props.ts | 114 +-- .../lib/forms/react-form-renderer.ts | 13 +- .../codegen-ui-react/lib/utils/graphql.ts | 9 +- 5 files changed, 896 insertions(+), 53 deletions(-) diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap index bbcc9a54..eb8f8b44 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 @@ -3521,6 +3521,796 @@ export default function BookCreateForm( " `; +exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Badge, + Button, + Divider, + Flex, + Grid, + GridProps, + Icon, + ScrollView, + Text, + TextAreaField, + TextAreaFieldProps, + TextField, + TextFieldProps, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { + EscapeHatchProps, + getOverrideProps, +} from \\"@aws-amplify/ui-react/internal\\"; +import { Post } from \\"../API\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { API } from \\"aws-amplify\\"; +import { getPost } from \\"../graphql/queries\\"; +import { updatePost } from \\"../graphql/mutations\\"; + +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = ( + value: T, + validationResponse: ValidationResponse +) => ValidationResponse | Promise; +export declare type MyPostFormInputValues = { + TextAreaFieldbbd63464?: string; + caption?: string; + username?: string; + profile_url?: string; + post_url?: string; + metadata?: string; + nonModelField?: string; + nonModelFieldArray?: string[]; +}; +export declare type MyPostFormValidationValues = { + TextAreaFieldbbd63464?: ValidationFunction; + caption?: ValidationFunction; + username?: ValidationFunction; + profile_url?: ValidationFunction; + post_url?: ValidationFunction; + metadata?: ValidationFunction; + nonModelField?: ValidationFunction; + nonModelFieldArray?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & + React.DOMAttributes; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: PrimitiveOverrideProps; + TextAreaFieldbbd63464?: PrimitiveOverrideProps; + caption?: PrimitiveOverrideProps; + username?: PrimitiveOverrideProps; + profile_url?: PrimitiveOverrideProps; + post_url?: PrimitiveOverrideProps; + metadata?: PrimitiveOverrideProps; + nonModelField?: PrimitiveOverrideProps; + nonModelFieldArray?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export type MyPostFormProps = React.PropsWithChildren< + { + overrides?: MyPostFormOverridesProps | undefined | null; + } & { + id?: string; + post?: Post; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; + } & React.CSSProperties +>; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function MyPostForm(props: MyPostFormProps): React.ReactElement { + const { + id: idProp, + post: postModelProp, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + TextAreaFieldbbd63464: \\"\\", + caption: \\"\\", + username: \\"\\", + profile_url: \\"\\", + post_url: \\"\\", + metadata: \\"\\", + nonModelField: \\"\\", + nonModelFieldArray: [], + }; + const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( + initialValues.TextAreaFieldbbd63464 + ); + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState(initialValues.metadata); + const [nonModelField, setNonModelField] = React.useState( + initialValues.nonModelField + ); + const [nonModelFieldArray, setNonModelFieldArray] = React.useState( + initialValues.nonModelFieldArray + ); + const [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) + ); + setNonModelField( + typeof cleanValues.nonModelField === \\"string\\" + ? cleanValues.nonModelField + : JSON.stringify(cleanValues.nonModelField) + ); + setNonModelFieldArray( + cleanValues.nonModelFieldArray?.map((item) => + typeof item === \\"string\\" ? item : JSON.stringify(item) + ) ?? [] + ); + setCurrentNonModelFieldArrayValue(\\"\\"); + setErrors({}); + }; + const [postRecord, setPostRecord] = React.useState(postModelProp); + React.useEffect(() => { + const queryData = async () => { + const record = idProp + ? await API.graphql({ + query: getPost, + variables: { + input: { + id: idProp, + }, + }, + }) + : postModelProp; + setPostRecord(record); + }; + queryData(); + }, [idProp, postModelProp]); + React.useEffect(resetStateValues, [postRecord]); + const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = + React.useState(\\"\\"); + const nonModelFieldArrayRef = React.createRef(); + const validations = { + TextAreaFieldbbd63464: [], + caption: [], + username: [], + profile_url: [{ type: \\"URL\\" }], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], + nonModelField: [{ type: \\"JSON\\" }], + nonModelFieldArray: [{ type: \\"JSON\\" }], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + return ( + /* @ts-ignore: TS2322 */ + { + event.preventDefault(); + let modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value.trim() === \\"\\") { + modelFields[key] = undefined; + } + }); + const modelFieldsToSave = { + caption: modelFields.caption, + username: modelFields.username, + profile_url: modelFields.profile_url, + post_url: modelFields.post_url, + metadata: modelFields.metadata, + nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => + JSON.parse(s) + ), + nonModelField: modelFields.nonModelField + ? JSON.parse(modelFields.nonModelField) + : modelFields.nonModelField, + }; + await API.graphql({ + query: updatePost, + variables: { + input: { + ...modelFieldsToSave, + }, + }, + }); + if (onSuccess) { + onSuccess(modelFields); + } + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...rest} + > + + + + + + + + { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464: value, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.TextAreaFieldbbd63464 ?? value; + } + if (errors.TextAreaFieldbbd63464?.hasError) { + runValidationTasks(\\"TextAreaFieldbbd63464\\", value); + } + setTextAreaFieldbbd63464(value); + }} + onBlur={() => + runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) + } + errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} + hasError={errors.TextAreaFieldbbd63464?.hasError} + {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption: value, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.caption ?? value; + } + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); + } + setCaption(value); + }} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username: value, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.username ?? value; + } + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); + } + setUsername(value); + }} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url: value, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url: value, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; + } + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata: value, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.metadata ?? value; + } + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); + } + setMetadata(value); + }} + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField: value, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.nonModelField ?? value; + } + if (errors.nonModelField?.hasError) { + runValidationTasks(\\"nonModelField\\", value); + } + setNonModelField(value); + }} + onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} + errorMessage={errors.nonModelField?.errorMessage} + hasError={errors.nonModelField?.hasError} + {...getOverrideProps(overrides, \\"nonModelField\\")} + > + { + let values = items; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray: values, + }; + const result = onChange(modelFields); + values = result?.nonModelFieldArray ?? values; + } + setNonModelFieldArray(values); + setCurrentNonModelFieldArrayValue(\\"\\"); + }} + currentFieldValue={currentNonModelFieldArrayValue} + label={\\"Non model field array\\"} + items={nonModelFieldArray} + hasError={errors?.nonModelFieldArray?.hasError} + errorMessage={errors?.nonModelFieldArray?.errorMessage} + setFieldValue={setCurrentNonModelFieldArrayValue} + inputFieldRef={nonModelFieldArrayRef} + defaultFieldValue={\\"\\"} + > + { + let { value } = e.target; + if (errors.nonModelFieldArray?.hasError) { + runValidationTasks(\\"nonModelFieldArray\\", value); + } + setCurrentNonModelFieldArrayValue(value); + }} + onBlur={() => + runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) + } + errorMessage={errors.nonModelFieldArray?.errorMessage} + hasError={errors.nonModelFieldArray?.hasError} + ref={nonModelFieldArrayRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} + > + + + + + + + + + + ); +} +" +`; + exports[`amplify form renderer tests GraphQL form tests should render a create form for child of 1:m relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts index ba10a39f..5a6c3cea 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 @@ -718,6 +718,29 @@ describe('amplify form renderer tests', () => { expect(componentText).toMatchSnapshot(); }); + it('should generate a update form without relationships', () => { + const { componentText } = generateWithAmplifyFormRenderer( + 'forms/post-datastore-update', + 'datastore/post', + rendererConfigWithGraphQL, + { isNonModelSupported: true, isRelationshipSupported: false }, + ); + + // check import for graphql operations + expect(componentText).toContain('import { API } from "aws-amplify";'); + expect(componentText).toContain('import { getPost } from "../graphql/queries";'); + expect(componentText).toContain('import { updatePost } from "../graphql/mutations";'); + + // should not have DataStore.save call + expect(componentText).not.toContain('await DataStore.save('); + + // should call updatePost mutation onSubmit + expect(componentText).toContain(`await API.graphql`); + expect(componentText).toContain(`query: updatePost,`); + + expect(componentText).toMatchSnapshot(); + }); + it('should generate a create form with hasOne relationship', () => { const { componentText } = generateWithAmplifyFormRenderer( 'forms/book-datastore-relationship', diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts index 58ab4a81..9fad7d81 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/cta-props.ts @@ -71,11 +71,15 @@ const getRecordUpdateDataStoreCallExpression = ({ modelName, importedModelName, fieldConfigs, + importCollection, + dataApi, }: { savedObjectName: string; modelName: string; importedModelName: string; fieldConfigs: Record; + importCollection: ImportCollection; + dataApi?: DataApiKind; }) => { const updatedObjectName = 'updated'; // TODO: remove after DataStore addresses issue: https://github.com/aws-amplify/amplify-js/issues/10750 @@ -86,6 +90,12 @@ const getRecordUpdateDataStoreCallExpression = ({ fieldConfigs, }); + if (dataApi === 'GraphQL') { + const inputs = [factory.createSpreadAssignment(factory.createIdentifier(savedObjectName))]; + + return getGraphqlCallExpression(ActionType.UPDATE, importedModelName, importCollection, inputs); + } + return factory.createCallExpression( factory.createPropertyAccessExpression(factory.createIdentifier('DataStore'), factory.createIdentifier('save')), undefined, @@ -332,6 +342,45 @@ export const buildExpression = ( ); } + const resolvePromisesStatement = factory.createExpressionStatement( + factory.createAwaitExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression(factory.createIdentifier('Promise'), factory.createIdentifier('all')), + undefined, + [factory.createIdentifier('promises')], + ), + ), + ); + + if (dataStoreActionType === 'update') { + const recordUpdateDataStoreCallExpression = getRecordUpdateDataStoreCallExpression({ + savedObjectName, + modelName, + importedModelName, + fieldConfigs, + importCollection, + dataApi, + }); + + const genericUpdateStatement = relationshipsPromisesAccessStatements.length + ? [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('promises'), + factory.createIdentifier('push'), + ), + undefined, + [recordUpdateDataStoreCallExpression], + ), + ), + resolvePromisesStatement, + ] + : [factory.createExpressionStatement(factory.createAwaitExpression(recordUpdateDataStoreCallExpression))]; + + return [...relationshipsPromisesAccessStatements, ...modelObjectToSaveStatements, ...genericUpdateStatement]; + } + const recordCreateCallExpression = getRecordCreateCallExpression({ savedObjectName, importedModelName, @@ -356,43 +405,6 @@ export const buildExpression = ( ), ] : [factory.createExpressionStatement(factory.createAwaitExpression(recordCreateCallExpression))]; - - const resolvePromisesStatement = factory.createExpressionStatement( - factory.createAwaitExpression( - factory.createCallExpression( - factory.createPropertyAccessExpression(factory.createIdentifier('Promise'), factory.createIdentifier('all')), - undefined, - [factory.createIdentifier('promises')], - ), - ), - ); - - const recordUpdateDataStoreCallExpression = getRecordUpdateDataStoreCallExpression({ - savedObjectName, - modelName, - importedModelName, - fieldConfigs, - }); - - const genericUpdateStatement = relationshipsPromisesAccessStatements.length - ? [ - factory.createExpressionStatement( - factory.createCallExpression( - factory.createPropertyAccessExpression( - factory.createIdentifier('promises'), - factory.createIdentifier('push'), - ), - undefined, - [recordUpdateDataStoreCallExpression], - ), - ), - resolvePromisesStatement, - ] - : [factory.createExpressionStatement(factory.createAwaitExpression(recordUpdateDataStoreCallExpression))]; - - if (dataStoreActionType === 'update') { - return [...relationshipsPromisesAccessStatements, ...modelObjectToSaveStatements, ...genericUpdateStatement]; - } const createStatements = [ ...modelObjectToSaveStatements, ...genericCreateStatement, @@ -646,8 +658,25 @@ export const buildUpdateDatastoreQuery = ( lowerCaseDataTypeName: string, relatedModelStatements: Statement[], primaryKey: string, + importCollection: ImportCollection, + dataApi?: DataApiKind, ) => { const pkQueryIdentifier = factory.createIdentifier(primaryKey); + + const queryCall = + dataApi === 'GraphQL' + ? getGraphqlCallExpression(ActionType.GET, importedModelName, importCollection, [ + factory.createPropertyAssignment(factory.createIdentifier('id'), factory.createIdentifier('idProp')), + ]) + : factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('DataStore'), + factory.createIdentifier('query'), + ), + undefined, + [factory.createIdentifier(importedModelName), pkQueryIdentifier], + ); + return [ factory.createVariableStatement( undefined, @@ -676,16 +705,7 @@ export const buildUpdateDatastoreQuery = ( factory.createConditionalExpression( pkQueryIdentifier, factory.createToken(SyntaxKind.QuestionToken), - factory.createAwaitExpression( - factory.createCallExpression( - factory.createPropertyAccessExpression( - factory.createIdentifier('DataStore'), - factory.createIdentifier('query'), - ), - undefined, - [factory.createIdentifier(importedModelName), pkQueryIdentifier], - ), - ), + factory.createAwaitExpression(queryCall), factory.createToken(SyntaxKind.ColonToken), factory.createIdentifier(getModelNameProp(lowerCaseDataTypeName)), ), diff --git a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts index de271c60..ada37021 100644 --- a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts +++ b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts @@ -388,6 +388,8 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< const lowerCaseDataTypeName = lowerCaseFirst(dataTypeName); const lowerCaseDataTypeNameRecord = `${lowerCaseDataTypeName}Record`; const isDataStoreUpdateForm = dataSourceType === 'DataStore' && formActionType === 'update'; + const dataApi = 'apiConfiguration' in this.renderConfig ? this.renderConfig.apiConfiguration?.dataApi : undefined; + let modelName = dataTypeName; if (!formMetadata) { throw new Error(`Form Metadata is missing from form: ${this.component.name}`); @@ -515,7 +517,14 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< this.primaryKeys.length > 1 ? getPropName(COMPOSITE_PRIMARY_KEY_PROP_NAME) : getPropName(this.primaryKeys[0]); statements.push( addUseEffectWrapper( - buildUpdateDatastoreQuery(modelName, lowerCaseDataTypeName, relatedModelStatements, destructuredPrimaryKey), + buildUpdateDatastoreQuery( + modelName, + lowerCaseDataTypeName, + relatedModelStatements, + destructuredPrimaryKey, + this.importCollection, + dataApi, + ), [destructuredPrimaryKey, getModelNameProp(lowerCaseDataTypeName)], ), ); @@ -617,8 +626,6 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< this.importCollection.addMappedImport(ImportValue.USE_DATA_STORE_BINDING); } - const dataApi = 'apiConfiguration' in this.renderConfig ? this.renderConfig.apiConfiguration?.dataApi : undefined; - statements.push( ...[...relatedModelNames].map((relatedModelName) => buildRelationshipQuery(relatedModelName, this.importCollection, dataApi), diff --git a/packages/codegen-ui-react/lib/utils/graphql.ts b/packages/codegen-ui-react/lib/utils/graphql.ts index 8b7d79ff..0a475121 100644 --- a/packages/codegen-ui-react/lib/utils/graphql.ts +++ b/packages/codegen-ui-react/lib/utils/graphql.ts @@ -16,7 +16,7 @@ import { plural } from 'pluralize'; import { InvalidInputError } from '@aws-amplify/codegen-ui'; -import { CallExpression, factory } from 'typescript'; +import { CallExpression, ObjectLiteralElementLike, factory } from 'typescript'; import { ImportCollection, ImportValue } from '../imports'; export enum ActionType { @@ -24,6 +24,7 @@ export enum ActionType { UPDATE = 'update', DELETE = 'delete', LIST = 'list', + GET = 'get', } export const getGraphqlQueryForModel = (action: ActionType, model: string): string => { @@ -34,6 +35,8 @@ export const getGraphqlQueryForModel = (action: ActionType, model: string): stri return `update${model}`; case ActionType.DELETE: return `delete${model}`; + case ActionType.GET: + return `get${model}`; case ActionType.LIST: return `list${plural(model)}`; default: @@ -60,13 +63,13 @@ export const getGraphqlCallExpression = ( action: ActionType, model: string, importCollection: ImportCollection, - inputs?: any[], + inputs?: ObjectLiteralElementLike[], ): CallExpression => { const query = getGraphqlQueryForModel(action, model); importCollection.addMappedImport(ImportValue.API); - if (action === ActionType.LIST) { + if (action === ActionType.LIST || action === ActionType.GET) { importCollection.addGraphqlQueryImport(query); } else { importCollection.addGraphqlMutationImport(query);