diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3c4b1428..560e0775 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -41,8 +41,7 @@ jobs: - name: Build amplify-cli working-directory: amplify-cli run: | - yarn policies set-version 1.18.0 - yarn --network-concurrency 1 + yarn install yarn setup-dev - name: Install updated codegen libraries working-directory: amplify-cli/packages/amplify-util-uibuilder @@ -52,9 +51,9 @@ jobs: - name: Build amplify-cli with updated codegen libraries working-directory: amplify-cli run: | - yarn policies set-version 1.18.0 - yarn --network-concurrency 1 - yarn dev-build + yarn install + yarn build + echo "$HOME/work/amplify-codegen-ui/amplify-codegen-ui/amplify-cli/.bin" >> $GITHUB_PATH - name: Create a test react app run: npx create-react-app e2e-test-app - name: Install test app dependencies @@ -112,7 +111,7 @@ jobs: - name: Install packages run: npm ci - name: Lerna bootstrap - run: lerna bootstrap + run: npm run bootstrap - name: Setup Integration Test run: npm run integ:setup - name: Cypress run diff --git a/CHANGELOG.md b/CHANGELOG.md index f103f7f9..1deea961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.14.2](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.1...v2.14.2) (2023-06-06) + +### Bug Fixes + +- add getOverrideProps to StorageManager ([#1020](https://github.com/aws-amplify/amplify-codegen-ui/issues/1020)) ([8ff282f](https://github.com/aws-amplify/amplify-codegen-ui/commit/8ff282f5a5b2f42dbbb20737d1060a3f5e60cc90)) + +## [2.14.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.0...v2.14.1) (2023-05-23) + +### Bug Fixes + +- fix storage field prop import to storagemanager ([#1016](https://github.com/aws-amplify/amplify-codegen-ui/issues/1016)) ([adf26f4](https://github.com/aws-amplify/amplify-codegen-ui/commit/adf26f43fe6823ba15480a81a2513bb2a98bd7b2)) + +# [2.14.0](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.14.0) (2023-05-22) + +### Bug Fixes + +- **codegen-ui:** fix multiple has one relationships ([#1005](https://github.com/aws-amplify/amplify-codegen-ui/issues/1005)) ([96b7b9d](https://github.com/aws-amplify/amplify-codegen-ui/commit/96b7b9dc3e9e70e1d8527d9abb59334b0bb56ca7)) +- parse operand value when field is number type ([#1013](https://github.com/aws-amplify/amplify-codegen-ui/issues/1013)) ([ad9ba60](https://github.com/aws-amplify/amplify-codegen-ui/commit/ad9ba606ab381b2b4759f19f9310a0901dd10fa8)) +- upgrade node and pin lerna version in circleci ([#1002](https://github.com/aws-amplify/amplify-codegen-ui/issues/1002)) ([2c70073](https://github.com/aws-amplify/amplify-codegen-ui/commit/2c700733928c28348ab100832b3c9e4fb56ef8a5)) + +### Features + +- exporting processFile to be used in studio ([#1001](https://github.com/aws-amplify/amplify-codegen-ui/issues/1001)) ([ff1ef27](https://github.com/aws-amplify/amplify-codegen-ui/commit/ff1ef272dd0b2192217419300353f8b7388aa26c)) + ## [2.13.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.13.1) (2023-05-02) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 141c6f59..980574f9 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,7 @@ { "packages": ["packages/*"], "exact": true, - "version": "2.13.1", + "version": "2.14.2", "command": { "version": { "message": "chore(release): %s" diff --git a/package.json b/package.json index 666d1042..fc5ad20d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "integ:test": "run-script-os", "integ:test:default": "./scripts/integ-test.sh", "integ:test:win32": "scripts\\integ-test.bat", - "integ:clean": "npx rimraf packages/integration-test" + "integ:clean": "npx rimraf packages/integration-test", + "bootstrap": "lerna bootstrap" }, "files": [], "devDependencies": { diff --git a/packages/codegen-ui-golden-files/CHANGELOG.md b/packages/codegen-ui-golden-files/CHANGELOG.md index 39b7e69a..eb7b5a90 100644 --- a/packages/codegen-ui-golden-files/CHANGELOG.md +++ b/packages/codegen-ui-golden-files/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.14.2](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.1...v2.14.2) (2023-06-06) + +**Note:** Version bump only for package @aws-amplify/codegen-ui-golden-files + +## [2.14.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.0...v2.14.1) (2023-05-23) + +**Note:** Version bump only for package @aws-amplify/codegen-ui-golden-files + +# [2.14.0](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.14.0) (2023-05-22) + +**Note:** Version bump only for package @aws-amplify/codegen-ui-golden-files + ## [2.13.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.13.1) (2023-05-02) **Note:** Version bump only for package @aws-amplify/codegen-ui-golden-files diff --git a/packages/codegen-ui-golden-files/package-lock.json b/packages/codegen-ui-golden-files/package-lock.json index 13f99621..1d307298 100644 --- a/packages/codegen-ui-golden-files/package-lock.json +++ b/packages/codegen-ui-golden-files/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aws-amplify/codegen-ui-golden-files", - "version": "2.13.1", + "version": "2.14.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aws-amplify/codegen-ui-golden-files", - "version": "2.13.1", + "version": "2.14.2", "license": "Apache-2.0", "dependencies": { "@aws-amplify/datastore": "^3.12.12", diff --git a/packages/codegen-ui-golden-files/package.json b/packages/codegen-ui-golden-files/package.json index 813209ce..ab3769e2 100644 --- a/packages/codegen-ui-golden-files/package.json +++ b/packages/codegen-ui-golden-files/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/codegen-ui-golden-files", - "version": "2.13.1", + "version": "2.14.2", "description": "Models of outputs to customer project", "author": "Amazon Web Services", "homepage": "https://docs.amplify.aws/", @@ -11,7 +11,7 @@ "typescript": "^4.8.4" }, "dependencies": { - "@aws-amplify/codegen-ui": "2.13.1", + "@aws-amplify/codegen-ui": "2.14.2", "@aws-amplify/datastore": "^3.12.12", "@aws-amplify/ui-react": "^3.5.7", "aws-amplify": "^4.3.37", diff --git a/packages/codegen-ui-react/CHANGELOG.md b/packages/codegen-ui-react/CHANGELOG.md index b211d7a4..f58efef2 100644 --- a/packages/codegen-ui-react/CHANGELOG.md +++ b/packages/codegen-ui-react/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.14.2](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.1...v2.14.2) (2023-06-06) + +### Bug Fixes + +- add getOverrideProps to StorageManager ([#1020](https://github.com/aws-amplify/amplify-codegen-ui/issues/1020)) ([8ff282f](https://github.com/aws-amplify/amplify-codegen-ui/commit/8ff282f5a5b2f42dbbb20737d1060a3f5e60cc90)) + +## [2.14.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.0...v2.14.1) (2023-05-23) + +### Bug Fixes + +- fix storage field prop import to storagemanager ([#1016](https://github.com/aws-amplify/amplify-codegen-ui/issues/1016)) ([adf26f4](https://github.com/aws-amplify/amplify-codegen-ui/commit/adf26f43fe6823ba15480a81a2513bb2a98bd7b2)) + +# [2.14.0](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.14.0) (2023-05-22) + +### Bug Fixes + +- **codegen-ui:** fix multiple has one relationships ([#1005](https://github.com/aws-amplify/amplify-codegen-ui/issues/1005)) ([96b7b9d](https://github.com/aws-amplify/amplify-codegen-ui/commit/96b7b9dc3e9e70e1d8527d9abb59334b0bb56ca7)) +- parse operand value when field is number type ([#1013](https://github.com/aws-amplify/amplify-codegen-ui/issues/1013)) ([ad9ba60](https://github.com/aws-amplify/amplify-codegen-ui/commit/ad9ba606ab381b2b4759f19f9310a0901dd10fa8)) + +### Features + +- exporting processFile to be used in studio ([#1001](https://github.com/aws-amplify/amplify-codegen-ui/issues/1001)) ([ff1ef27](https://github.com/aws-amplify/amplify-codegen-ui/commit/ff1ef272dd0b2192217419300353f8b7388aa26c)) + ## [2.13.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.13.1) (2023-05-02) **Note:** Version bump only for package @aws-amplify/codegen-ui-react diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/react-theme-studio-template-renderer.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/react-theme-studio-template-renderer.test.ts.snap index 9ee9d578..cff61c2a 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/react-theme-studio-template-renderer.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/react-theme-studio-template-renderer.test.ts.snap @@ -91,6 +91,12 @@ export default createTheme({ " `; +exports[`react theme renderer tests theme should render the theme with ES5 2`] = ` +"declare const _default: any; +export default _default; +" +`; + exports[`react theme renderer tests theme should render the theme with TSX 1`] = ` "/* eslint-disable */ import { createTheme } from \\"@aws-amplify/ui-react\\"; 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 71bdedf3..f99e3880 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 @@ -26223,6 +26223,619 @@ export default function SchoolUpdateForm(props: SchoolUpdateFormProps): React.Re " `; +exports[`amplify form renderer tests datastore form tests should generate a update form with hasMany relationship with model name collision 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { + getOverrideProps, + useDataStoreBinding, +} from \\"@aws-amplify/ui-react/internal\\"; +import { School, Student as Student0 } 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, + 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 SchoolUpdateForm(props) { + const { + id: idProp, + school: schoolModelProp, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + name: \\"\\", + Student: [], + Students: undefined, + }; + const [name, setName] = React.useState(initialValues.name); + const [Student, setStudent] = React.useState(initialValues.Student); + const [Students, setStudents] = React.useState(initialValues.Students); + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + const cleanValues = schoolRecord + ? { ...initialValues, ...schoolRecord, Student: linkedStudent } + : initialValues; + setName(cleanValues.name); + setStudent(cleanValues.Student ?? []); + setCurrentStudentValue(undefined); + setCurrentStudentDisplayValue(\\"\\"); + setStudents(cleanValues.Students); + setErrors({}); + }; + const [schoolRecord, setSchoolRecord] = React.useState(schoolModelProp); + const [linkedStudent, setLinkedStudent] = React.useState([]); + const canUnlinkStudent = false; + React.useEffect(() => { + const queryData = async () => { + const record = idProp + ? await DataStore.query(School, idProp) + : schoolModelProp; + setSchoolRecord(record); + const linkedStudent = record ? await record.Student.toArray() : []; + setLinkedStudent(linkedStudent); + }; + queryData(); + }, [idProp, schoolModelProp]); + React.useEffect(resetStateValues, [schoolRecord, linkedStudent]); + const [currentStudentDisplayValue, setCurrentStudentDisplayValue] = + React.useState(\\"\\"); + const [currentStudentValue, setCurrentStudentValue] = + React.useState(undefined); + const StudentRef = React.createRef(); + const getIDValue = { + Student: (r) => JSON.stringify({ id: r?.id }), + }; + const StudentIdSet = new Set( + Array.isArray(Student) + ? Student.map((r) => getIDValue.Student?.(r)) + : getIDValue.Student?.(Student) + ); + const studentRecords = useDataStoreBinding({ + type: \\"collection\\", + model: Student0, + }).items; + const getDisplayValue = { + Student: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, + }; + const validations = { + name: [], + Student: [], + Students: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + return ( + { + event.preventDefault(); + let modelFields = { + name, + Student, + Students, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value.trim() === \\"\\") { + modelFields[key] = undefined; + } + }); + const promises = []; + const studentToLink = []; + const studentToUnLink = []; + const studentSet = new Set(); + const linkedStudentSet = new Set(); + Student.forEach((r) => studentSet.add(getIDValue.Student?.(r))); + linkedStudent.forEach((r) => + linkedStudentSet.add(getIDValue.Student?.(r)) + ); + linkedStudent.forEach((r) => { + if (!studentSet.has(getIDValue.Student?.(r))) { + studentToUnLink.push(r); + } + }); + Student.forEach((r) => { + if (!linkedStudentSet.has(getIDValue.Student?.(r))) { + studentToLink.push(r); + } + }); + studentToUnLink.forEach((original) => { + if (!canUnlinkStudent) { + throw Error( + \`Student \${original.id} cannot be unlinked from School because schoolID is a required field.\` + ); + } + promises.push( + DataStore.save( + Student0.copyOf(original, (updated) => { + updated.schoolID = null; + }) + ) + ); + }); + studentToLink.forEach((original) => { + promises.push( + DataStore.save( + Student0.copyOf(original, (updated) => { + updated.schoolID = schoolRecord.id; + }) + ) + ); + }); + const modelFieldsToSave = { + name: modelFields.name, + }; + promises.push( + DataStore.save( + School.copyOf(schoolRecord, (updated) => { + Object.assign(updated, modelFieldsToSave); + }) + ) + ); + await Promise.all(promises); + if (onSuccess) { + onSuccess(modelFields); + } + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...getOverrideProps(overrides, \\"SchoolUpdateForm\\")} + {...rest} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + name: value, + Student, + Students, + }; + const result = onChange(modelFields); + value = result?.name ?? value; + } + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); + } + setName(value); + }} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} + > + { + let values = items; + if (onChange) { + const modelFields = { + name, + Student: values, + Students, + }; + const result = onChange(modelFields); + values = result?.Student ?? values; + } + setStudent(values); + setCurrentStudentValue(undefined); + setCurrentStudentDisplayValue(\\"\\"); + }} + currentFieldValue={currentStudentValue} + label={\\"Student\\"} + items={Student} + hasError={errors?.Student?.hasError} + errorMessage={errors?.Student?.errorMessage} + getBadgeText={getDisplayValue.Student} + setFieldValue={(model) => { + setCurrentStudentDisplayValue( + model ? getDisplayValue.Student(model) : \\"\\" + ); + setCurrentStudentValue(model); + }} + inputFieldRef={StudentRef} + defaultFieldValue={\\"\\"} + > + !StudentIdSet.has(getIDValue.Student?.(r))) + .map((r) => ({ + id: getIDValue.Student?.(r), + label: getDisplayValue.Student?.(r), + }))} + onSelect={({ id, label }) => { + setCurrentStudentValue( + studentRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentStudentDisplayValue(label); + runValidationTasks(\\"Student\\", label); + }} + onClear={() => { + setCurrentStudentDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + if (errors.Student?.hasError) { + runValidationTasks(\\"Student\\", value); + } + setCurrentStudentDisplayValue(value); + setCurrentStudentValue(undefined); + }} + onBlur={() => + runValidationTasks(\\"Student\\", currentStudentDisplayValue) + } + errorMessage={errors.Student?.errorMessage} + hasError={errors.Student?.hasError} + ref={StudentRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"Student\\")} + > + + arr.findIndex((member) => member?.id === r?.id) === i + ) + .map((r) => ({ + id: r?.id, + label: getDisplayValue.Students?.(r), + }))} + onSelect={({ id, label }) => { + setStudents(id); + runValidationTasks(\\"Students\\", id); + }} + onClear={() => { + setStudents(\\"\\"); + }} + defaultValue={Students} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + name, + Student, + Students: value, + }; + const result = onChange(modelFields); + value = result?.Students ?? value; + } + if (errors.Students?.hasError) { + runValidationTasks(\\"Students\\", value); + } + setStudents(value); + }} + onBlur={() => runValidationTasks(\\"Students\\", Students)} + errorMessage={errors.Students?.errorMessage} + hasError={errors.Students?.hasError} + labelHidden={false} + {...getOverrideProps(overrides, \\"Students\\")} + > + + + + + + + + + ); +} +" +`; + +exports[`amplify form renderer tests datastore form tests should generate a update form with hasMany relationship with model name collision 2`] = ` +"import * as React from \\"react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { School, Student as Student0 } from \\"../models\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type SchoolUpdateFormInputValues = { + name?: string; + Student?: Student0[]; + Students?: string; +}; +export declare type SchoolUpdateFormValidationValues = { + name?: ValidationFunction; + Student?: ValidationFunction; + Students?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type SchoolUpdateFormOverridesProps = { + SchoolUpdateFormGrid?: PrimitiveOverrideProps; + name?: PrimitiveOverrideProps; + Student?: PrimitiveOverrideProps; + Students?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type SchoolUpdateFormProps = React.PropsWithChildren<{ + overrides?: SchoolUpdateFormOverridesProps | undefined | null; +} & { + id?: string; + school?: School; + onSubmit?: (fields: SchoolUpdateFormInputValues) => SchoolUpdateFormInputValues; + onSuccess?: (fields: SchoolUpdateFormInputValues) => void; + onError?: (fields: SchoolUpdateFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: SchoolUpdateFormInputValues) => SchoolUpdateFormInputValues; + onValidate?: SchoolUpdateFormValidationValues; +} & React.CSSProperties>; +export default function SchoolUpdateForm(props: SchoolUpdateFormProps): React.ReactElement; +" +`; + exports[`amplify form renderer tests datastore form tests should generate an update form with belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; @@ -32512,10 +33125,10 @@ exports[`amplify form renderer tests forms with StorageField tests should render "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; +import { StorageManager } from \\"@aws-amplify/ui-react-storage\\"; import { Field, getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Product } from \\"../models\\"; import { fetchByPath, processFile, validateField } from \\"./utils\\"; -import { StorageManager } from \\"@aws-amplify/ui-react-storage\\"; import { DataStore } from \\"aws-amplify\\"; export default function CreateProductForm(props) { const { @@ -32685,6 +33298,7 @@ export default function CreateProductForm(props) { showThumbnails={false} maxFileCount={5} maxSize={1024} + {...getOverrideProps(overrides, \\"imgKeys\\")} > )} @@ -32951,14 +33566,54 @@ export default function UpdateProductForm(props) { " `; +exports[`amplify form renderer tests forms with StorageField tests should render a update form with StorageField 2`] = ` +"import * as React from \\"react\\"; +import { GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { StorageManagerProps } from \\"@aws-amplify/ui-react-storage\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Product } from \\"../models\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type UpdateProductFormInputValues = { + name?: string; + imgKeys?: string[]; +}; +export declare type UpdateProductFormValidationValues = { + name?: ValidationFunction; + imgKeys?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type UpdateProductFormOverridesProps = { + UpdateProductFormGrid?: PrimitiveOverrideProps; + name?: PrimitiveOverrideProps; + imgKeys?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type UpdateProductFormProps = React.PropsWithChildren<{ + overrides?: UpdateProductFormOverridesProps | undefined | null; +} & { + id?: string; + product?: Product; + onSubmit?: (fields: UpdateProductFormInputValues) => UpdateProductFormInputValues; + onSuccess?: (fields: UpdateProductFormInputValues) => void; + onError?: (fields: UpdateProductFormInputValues, errorMessage: string) => void; + onChange?: (fields: UpdateProductFormInputValues) => UpdateProductFormInputValues; + onValidate?: UpdateProductFormValidationValues; +} & React.CSSProperties>; +export default function UpdateProductForm(props: UpdateProductFormProps): React.ReactElement; +" +`; + exports[`amplify form renderer tests forms with StorageField tests should render a update form with StorageField on non-array field 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { Button, Flex, Grid, TextField } from \\"@aws-amplify/ui-react\\"; +import { StorageManager } from \\"@aws-amplify/ui-react-storage\\"; import { Field, getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Product } from \\"../models\\"; import { fetchByPath, processFile, validateField } from \\"./utils\\"; -import { StorageManager } from \\"@aws-amplify/ui-react-storage\\"; import { DataStore } from \\"aws-amplify\\"; export default function UpdateProductForm(props) { const { @@ -33149,6 +33804,7 @@ export default function UpdateProductForm(props) { showThumbnails={false} maxFileCount={1} maxSize={1024} + {...getOverrideProps(overrides, \\"singleImgKey\\")} > )} diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap index c0685d6f..aa2d346c 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap @@ -1262,7 +1262,7 @@ export default function CollectionOfCustomButtons( } = props; const itemsFilterObj = { and: [ - { field: \\"age\\", operand: \\"10\\", operator: \\"gt\\" }, + { field: \\"age\\", operand: 10, operator: \\"eq\\" }, { field: \\"lastName\\", operand: \\"L\\", operator: \\"beginsWith\\" }, ], }; diff --git a/packages/codegen-ui-react/lib/__tests__/__utils__/mock-data-schemas.ts b/packages/codegen-ui-react/lib/__tests__/__utils__/mock-data-schemas.ts index 78a17003..54eedc47 100644 --- a/packages/codegen-ui-react/lib/__tests__/__utils__/mock-data-schemas.ts +++ b/packages/codegen-ui-react/lib/__tests__/__utils__/mock-data-schemas.ts @@ -480,3 +480,90 @@ export const compositePersonSchema: GenericDataSchema = getGenericFromDataStore( codegenVersion: '3.3.2', version: 'accea0d7a2f24829740c710ceb3264a8', }); + +export const userSchema: GenericDataSchema = getGenericFromDataStore({ + models: { + User: { + name: 'User', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + firstName: { + name: 'firstName', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + lastName: { + name: 'lastName', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + age: { + name: 'age', + isArray: false, + type: 'Int', + isRequired: false, + attributes: [], + }, + isLoggedIn: { + name: 'isLoggedIn', + isArray: false, + type: 'Boolean', + isRequired: false, + attributes: [], + }, + loggedInColor: { + name: 'loggedInColor', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + loggedOutColor: { + name: 'loggedOutColor', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Users', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + }, + enums: {}, + nonModels: {}, + codegenVersion: '3.3.2', + version: 'accea0d7a2f24829740c710ceb3264a8', +}); diff --git a/packages/codegen-ui-react/lib/__tests__/react-component-render-helper.test.ts b/packages/codegen-ui-react/lib/__tests__/react-component-render-helper.test.ts index 9b53eab2..c179c35d 100644 --- a/packages/codegen-ui-react/lib/__tests__/react-component-render-helper.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/react-component-render-helper.test.ts @@ -35,6 +35,7 @@ import { buildConditionalExpression, hasChildrenProp, buildConcatExpression, + parseNumberOperand, } from '../react-component-render-helper'; import { assertASTMatchesSnapshot } from './__utils__'; @@ -345,4 +346,15 @@ describe('react-component-render-helper', () => { assertASTMatchesSnapshot(exp); }); }); + + describe('parseNumberOperand', () => { + test('should parse int if field data type is Int', () => { + expect(parseNumberOperand('10', { dataType: 'Int', readOnly: false, required: false, isArray: false })).toBe(10); + }); + test('should parse int if field data type is Int', () => { + expect(parseNumberOperand('10.01', { dataType: 'Float', readOnly: false, required: false, isArray: false })).toBe( + 10.01, + ); + }); + }); }); diff --git a/packages/codegen-ui-react/lib/__tests__/react-theme-studio-template-renderer.test.ts b/packages/codegen-ui-react/lib/__tests__/react-theme-studio-template-renderer.test.ts index db76c8d2..3c179cab 100644 --- a/packages/codegen-ui-react/lib/__tests__/react-theme-studio-template-renderer.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/react-theme-studio-template-renderer.test.ts @@ -25,11 +25,14 @@ function generateWithThemeRenderer( jsonFile: string, renderConfig: ReactRenderConfig = {}, options?: ReactThemeStudioTemplateRendererOptions, -): string { +) { const rendererFactory = new StudioTemplateRendererFactory( (theme: StudioTheme) => new ReactThemeStudioTemplateRenderer(theme, renderConfig, options), ); - return rendererFactory.buildRenderer(loadSchemaFromJSONFile(jsonFile)).renderComponent().componentText; + const { componentText, declaration } = rendererFactory + .buildRenderer(loadSchemaFromJSONFile(jsonFile)) + .renderComponent(); + return { componentText, declaration }; } function generateThemeObject(jsonFile: string): any { @@ -46,19 +49,28 @@ function generateThemeObject(jsonFile: string): any { describe('react theme renderer tests', () => { describe('theme', () => { it('should render the theme', () => { - expect(generateWithThemeRenderer('theme')).toMatchSnapshot(); + expect(generateWithThemeRenderer('theme').componentText).toMatchSnapshot(); }); it('should render the theme with TSX', () => { - expect(generateWithThemeRenderer('theme', { script: ScriptKind.TSX })).toMatchSnapshot(); + const { componentText, declaration } = generateWithThemeRenderer('theme', { script: ScriptKind.TSX }); + expect(componentText).toMatchSnapshot(); + expect(declaration).toBeUndefined(); }); it('should render the theme with ES5', () => { - expect(generateWithThemeRenderer('theme', { target: ScriptTarget.ES5, script: ScriptKind.JS })).toMatchSnapshot(); + const { componentText, declaration } = generateWithThemeRenderer('theme', { + target: ScriptTarget.ES5, + script: ScriptKind.JS, + renderTypeDeclarations: true, + }); + expect(componentText).toMatchSnapshot(); + expect(declaration).toBeDefined(); + expect(declaration).toMatchSnapshot(); }); it('should render the default theme', () => { - expect(generateWithThemeRenderer('theme', {}, { renderDefaultTheme: true })).toMatchSnapshot(); + expect(generateWithThemeRenderer('theme', {}, { renderDefaultTheme: true }).componentText).toMatchSnapshot(); }); }); 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 8f03494a..9023a1a3 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 @@ -240,6 +240,35 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should generate a update form with hasMany relationship with model name collision', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/school-datastore-update', + 'datastore/school-student-collision', + undefined, + { isNonModelSupported: true, isRelationshipSupported: true }, + ); + // check nested model is imported + expect(componentText).toContain('import { School, Student as Student0 } from "../models";'); + + // check binding call is generated + expect(componentText).toContain('const studentRecords = useDataStoreBinding({'); + + // check lazy load linked data + expect(componentText).toContain('const linkedStudent = record ? await record.Student.toArray() : [];'); + + // check custom display value is set + expect(componentText).toContain('Student: (r) => `${r?.name ? r?.name + " - " : ""}${r?.id}`,'); + + // check linked data useState is generate + expect(componentText).toContain('const [linkedStudent, setLinkedStudent] = React.useState([]);'); + + // check resetStateValues has correct dependencies + expect(componentText).toContain('React.useEffect(resetStateValues, [schoolRecord, linkedStudent]);'); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should render form with a two inputs in row', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/post-datastore-create-row', @@ -668,12 +697,13 @@ describe('amplify form renderer tests', () => { }); it('should render a update form with StorageField', () => { - const { componentText } = generateWithAmplifyFormRenderer( + const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/product-datastore-update', 'datastore/product', undefined, ); expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); }); }); diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts index 691e9530..399514a2 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts @@ -19,6 +19,7 @@ import { compositePersonSchema, generateWithAmplifyRenderer, rendererConfigWithGraphQL, + userSchema, } from './__utils__'; describe('amplify render tests', () => { @@ -131,7 +132,12 @@ describe('amplify render tests', () => { }); it('should render collection with data binding if binding name is items', () => { - const generatedCode = generateWithAmplifyRenderer('collectionWithBindingItemsName'); + const generatedCode = generateWithAmplifyRenderer( + 'collectionWithBindingItemsName', + undefined, + undefined, + userSchema, + ); expect(generatedCode.componentText).toMatchSnapshot(); }); diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts index 889f5b66..07b42342 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/relationship.ts @@ -23,7 +23,7 @@ import { } from '@aws-amplify/codegen-ui'; import { getRecordsName, getLinkedDataName, buildAccessChain, getCanUnlinkModelName } from './form-state'; import { buildBaseCollectionVariableStatement } from '../../react-studio-template-renderer-helper'; -import { ImportCollection } from '../../imports'; +import { ImportCollection, ImportSource } from '../../imports'; import { lowerCaseFirst, getSetNameIdentifier, capitalizeFirstLetter } from '../../helpers'; import { isManyToManyRelationship } from './map-from-fieldConfigs'; import { extractModelAndKeys, getIDValueCallChain, getMatchEveryModelFieldCallExpression } from './model-values'; @@ -1291,6 +1291,7 @@ export const buildHasManyRelationshipStatements = ( fieldName = fieldConfigMetaData.sanitizedFieldName || fieldName; const { relatedModelName, relatedModelFields, belongsToFieldOnRelatedModel } = fieldConfigMetaData.relationship as HasManyRelationshipType; + const relatedModelVariableName = importCollection.getMappedAlias(ImportSource.LOCAL_MODELS, relatedModelName); const linkedDataName = getLinkedDataName(fieldName); const dataToLink = `${lowerCaseFirst(fieldName)}ToLink`; const dataToUnLink = `${lowerCaseFirst(fieldName)}ToUnLink`; @@ -1629,7 +1630,7 @@ export const buildHasManyRelationshipStatements = ( [ factory.createCallExpression( factory.createPropertyAccessExpression( - factory.createIdentifier(relatedModelName), + factory.createIdentifier(relatedModelVariableName), factory.createIdentifier('copyOf'), ), undefined, @@ -1716,7 +1717,7 @@ export const buildHasManyRelationshipStatements = ( [ factory.createCallExpression( factory.createPropertyAccessExpression( - factory.createIdentifier(relatedModelName), + factory.createIdentifier(relatedModelVariableName), factory.createIdentifier('copyOf'), ), undefined, diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts index 6ebe10af..70d41002 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts @@ -493,7 +493,13 @@ export const buildOverrideTypesBindings = ( if (field.split('.').length > 1 || !isValidVariableName(field)) { propKey = factory.createStringLiteral(field); } - const componentTypePropName = `${formDefinition.elements[field].componentType}Props`; + let componentTypePropName = `${formDefinition.elements[field].componentType}Props`; + if (formDefinition.elements[field].componentType === 'StorageField') { + componentTypePropName = 'StorageManagerProps'; + importCollection.addImport(ImportSource.REACT_STORAGE, componentTypePropName); + } else { + importCollection.addImport(ImportSource.UI_REACT, componentTypePropName); + } typeNodes.push( factory.createPropertySignature( undefined, @@ -504,7 +510,6 @@ export const buildOverrideTypesBindings = ( ]), ), ); - importCollection.addImport(ImportSource.UI_REACT, componentTypePropName); }); }); diff --git a/packages/codegen-ui-react/lib/react-component-render-helper.ts b/packages/codegen-ui-react/lib/react-component-render-helper.ts index b32553d8..630456c8 100644 --- a/packages/codegen-ui-react/lib/react-component-render-helper.ts +++ b/packages/codegen-ui-react/lib/react-component-render-helper.ts @@ -52,7 +52,13 @@ import { ArrayLiteralExpression, } from 'typescript'; -import { FormMetadata, FormStyleConfig, StudioFormInputFieldProperty } from '@aws-amplify/codegen-ui/lib/types'; +import { + DataFieldDataType, + FormMetadata, + FormStyleConfig, + GenericDataField, + StudioFormInputFieldProperty, +} from '@aws-amplify/codegen-ui/lib/types'; import { ImportCollection } from './imports'; import { json, jsonToLiteral } from './react-studio-template-renderer-helper'; import { getChildPropMappingForComponentName } from './workflow/utils'; @@ -458,6 +464,19 @@ export function getSyntaxKindToken(operator: RelationalOperator): BinaryOperator } } +export function parseNumberOperand(operand: string | number | boolean, dataField: GenericDataField | undefined) { + if (dataField) { + const numberOperandType: DataFieldDataType[] = ['Int', 'Float']; + if (numberOperandType.includes(dataField.dataType)) { + const parsedOperand = parseFloat(`${operand}`); + if (!Number.isNaN(parsedOperand) && Number.isFinite(parsedOperand)) { + return parsedOperand; + } + } + } + return operand; +} + export function getConditionalOperandExpression( operand: string | number | boolean, operandType: string | undefined, diff --git a/packages/codegen-ui-react/lib/react-component-renderer.ts b/packages/codegen-ui-react/lib/react-component-renderer.ts index 530fc0a5..12c4999f 100644 --- a/packages/codegen-ui-react/lib/react-component-renderer.ts +++ b/packages/codegen-ui-react/lib/react-component-renderer.ts @@ -89,8 +89,8 @@ export class ReactComponentRenderer extends ComponentRendererBase< if (this.component.componentType === 'StorageField') { this.importCollection.addImport(ImportSource.REACT_STORAGE, 'StorageManager'); this.importCollection.addImport(ImportSource.UI_REACT_INTERNAL, 'Field'); + this.importCollection.addImport(ImportSource.UI_REACT_INTERNAL, ImportValue.GET_OVERRIDE_PROPS); this.importCollection.addImport(ImportSource.UTILS, 'processFile'); - return renderStorageFieldComponent( this.component, this.componentMetadata, diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts index 66207431..3d3d86ee 100644 --- a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts @@ -72,6 +72,7 @@ import { addBindingPropertiesImports, getComponentPropName, getConditionalOperandExpression, + parseNumberOperand, } from './react-component-render-helper'; import { transpile, @@ -936,7 +937,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer const modelName = this.importCollection.addModelImport(model); if (predicate) { - statements.push(this.buildPredicateDeclaration(propName, predicate)); + statements.push(this.buildPredicateDeclaration(propName, predicate, model)); statements.push(this.buildCreateDataStorePredicateCall(modelName, propName)); } if (sort) { @@ -1257,7 +1258,9 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer * operator: "eq", * } */ - statements.push(this.buildPredicateDeclaration(propName, bindingProperties.predicate)); + statements.push( + this.buildPredicateDeclaration(propName, bindingProperties.predicate, bindingProperties.model), + ); statements.push(this.buildCreateDataStorePredicateCall(modelName, propName)); /** * const buttonColorDataStore = useDataStoreBinding({ @@ -1438,11 +1441,14 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer ]); } - private predicateToObjectLiteralExpression(predicate: StudioComponentPredicate): ObjectLiteralExpression { + private predicateToObjectLiteralExpression( + predicate: StudioComponentPredicate, + model: string, + ): ObjectLiteralExpression { const { operandType, ...filteredPredicate } = predicate; if (filteredPredicate.operator === 'between') { - return this.predicateToObjectLiteralExpression(resolveBetweenPredicateToMultiplePredicates(predicate)); + return this.predicateToObjectLiteralExpression(resolveBetweenPredicateToMultiplePredicates(predicate), model); } const objectAssignments = Object.entries(filteredPredicate).map(([key, value]) => { @@ -1451,7 +1457,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer factory.createIdentifier(key), factory.createArrayLiteralExpression( (predicate[key] as StudioComponentPredicate[]).map( - (pred: StudioComponentPredicate) => this.predicateToObjectLiteralExpression(pred), + (pred: StudioComponentPredicate) => this.predicateToObjectLiteralExpression(pred, model), false, ), ), @@ -1460,7 +1466,13 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer if (key === 'operand' && typeof value === 'string') { return factory.createPropertyAssignment( factory.createIdentifier(key), - getConditionalOperandExpression(value, operandType), + getConditionalOperandExpression( + parseNumberOperand( + value, + this.componentMetadata.dataSchemaMetadata?.models[model]?.fields[predicate.field || ''], + ), + operandType, + ), ); } return factory.createPropertyAssignment( @@ -1488,7 +1500,11 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer return []; } - private buildPredicateDeclaration(name: string, predicate: StudioComponentPredicate): VariableStatement { + private buildPredicateDeclaration( + name: string, + predicate: StudioComponentPredicate, + model: string, + ): VariableStatement { return factory.createVariableStatement( undefined, factory.createVariableDeclarationList( @@ -1497,7 +1513,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer factory.createIdentifier(this.getFilterObjName(name)), undefined, undefined, - this.predicateToObjectLiteralExpression(predicate), + this.predicateToObjectLiteralExpression(predicate, model), ), ], ts.NodeFlags.Const, diff --git a/packages/codegen-ui-react/lib/react-theme-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-theme-studio-template-renderer.ts index 0f5f96ad..6f6d4adc 100644 --- a/packages/codegen-ui-react/lib/react-theme-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-theme-studio-template-renderer.ts @@ -53,6 +53,7 @@ export class ReactThemeStudioTemplateRenderer extends StudioTemplateRenderer< ReactOutputManager, { componentText: string; + declaration: string | undefined; renderComponentToFilesystem: (outputPath: string) => Promise; } > { @@ -86,6 +87,7 @@ export class ReactThemeStudioTemplateRenderer extends StudioTemplateRenderer< return { componentText: transpiledComponentText, + declaration, renderComponentToFilesystem: async (outputPath: string) => { await this.renderComponentToFilesystem(transpiledComponentText)(this.fileName)(outputPath); if (declaration) { diff --git a/packages/codegen-ui-react/lib/utils/forms/storage-field-component.ts b/packages/codegen-ui-react/lib/utils/forms/storage-field-component.ts index ff3ef314..c1a479ad 100644 --- a/packages/codegen-ui-react/lib/utils/forms/storage-field-component.ts +++ b/packages/codegen-ui-react/lib/utils/forms/storage-field-component.ts @@ -22,16 +22,7 @@ import { ComponentMetadata, isValidVariableName, } from '@aws-amplify/codegen-ui'; -import { - factory, - JsxAttribute, - JsxAttributeLike, - JsxChild, - JsxElement, - JsxExpression, - NodeFlags, - SyntaxKind, -} from 'typescript'; +import { factory, JsxAttributeLike, JsxChild, JsxElement, JsxExpression, NodeFlags, SyntaxKind } from 'typescript'; import { getDecoratedLabel } from '../../forms/form-renderer-helper'; import { buildStorageManagerOnChangeStatement } from '../../forms/form-renderer-helper/event-handler-props'; import { propertyToExpression } from '../../react-component-render-helper'; @@ -339,8 +330,8 @@ export const renderStorageFieldComponent = ( const lowerCaseDataTypeName = lowerCaseFirst(dataTypeName); const lowerCaseDataTypeNameRecord = `${lowerCaseDataTypeName}Record`; const storageManagerComponentName = factory.createIdentifier('StorageManager'); - const storageManagerAttributes: JsxAttribute[] = []; - const fieldAttributes: JsxAttribute[] = []; + const storageManagerAttributes: JsxAttributeLike[] = []; + const fieldAttributes: JsxAttributeLike[] = []; if (componentMetadata.formMetadata) { const errorKey = @@ -494,6 +485,15 @@ export const renderStorageFieldComponent = ( } }); + storageManagerAttributes.push( + factory.createJsxSpreadAttribute( + factory.createCallExpression(factory.createIdentifier('getOverrideProps'), undefined, [ + factory.createIdentifier('overrides'), + factory.createStringLiteral(componentName), + ]), + ), + ); + const storageManager = factory.createJsxElement( factory.createJsxOpeningElement( storageManagerComponentName, diff --git a/packages/codegen-ui-react/package-lock.json b/packages/codegen-ui-react/package-lock.json index 7fdf9783..da02a68e 100644 --- a/packages/codegen-ui-react/package-lock.json +++ b/packages/codegen-ui-react/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aws-amplify/codegen-ui-react", - "version": "2.13.1", + "version": "2.14.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aws-amplify/codegen-ui-react", - "version": "2.13.1", + "version": "2.14.2", "license": "Apache-2.0", "dependencies": { "@aws-amplify/codegen-ui": "2.13.1", diff --git a/packages/codegen-ui-react/package.json b/packages/codegen-ui-react/package.json index 30e3c593..f0a90cb2 100644 --- a/packages/codegen-ui-react/package.json +++ b/packages/codegen-ui-react/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/codegen-ui-react", - "version": "2.13.1", + "version": "2.14.2", "description": "Amplify UI React code generation implementation", "author": "Amazon Web Services", "repository": "https://github.com/aws-amplify/amplify-codegen-ui.git", @@ -32,7 +32,7 @@ "semver": "^7.3.5" }, "dependencies": { - "@aws-amplify/codegen-ui": "2.13.1", + "@aws-amplify/codegen-ui": "2.14.2", "@typescript/vfs": "~1.3.5", "pluralize": "^8.0.0", "typescript": "<=4.5.0" diff --git a/packages/codegen-ui/CHANGELOG.md b/packages/codegen-ui/CHANGELOG.md index 0fd8e2e6..9c412b5a 100644 --- a/packages/codegen-ui/CHANGELOG.md +++ b/packages/codegen-ui/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.14.2](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.1...v2.14.2) (2023-06-06) + +**Note:** Version bump only for package @aws-amplify/codegen-ui + +## [2.14.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.0...v2.14.1) (2023-05-23) + +**Note:** Version bump only for package @aws-amplify/codegen-ui + +# [2.14.0](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.14.0) (2023-05-22) + +### Bug Fixes + +- **codegen-ui:** fix multiple has one relationships ([#1005](https://github.com/aws-amplify/amplify-codegen-ui/issues/1005)) ([96b7b9d](https://github.com/aws-amplify/amplify-codegen-ui/commit/96b7b9dc3e9e70e1d8527d9abb59334b0bb56ca7)) +- parse operand value when field is number type ([#1013](https://github.com/aws-amplify/amplify-codegen-ui/issues/1013)) ([ad9ba60](https://github.com/aws-amplify/amplify-codegen-ui/commit/ad9ba606ab381b2b4759f19f9310a0901dd10fa8)) + ## [2.13.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.13.1) (2023-05-02) **Note:** Version bump only for package @aws-amplify/codegen-ui diff --git a/packages/codegen-ui/example-schemas/collectionWithBindingItemsName.json b/packages/codegen-ui/example-schemas/collectionWithBindingItemsName.json index 9e792eac..3a4a365d 100644 --- a/packages/codegen-ui/example-schemas/collectionWithBindingItemsName.json +++ b/packages/codegen-ui/example-schemas/collectionWithBindingItemsName.json @@ -45,7 +45,7 @@ { "field": "age", "operand": "10", - "operator": "gt" + "operator": "eq" }, { "field": "lastName", diff --git a/packages/codegen-ui/example-schemas/datastore/school-student-collision.json b/packages/codegen-ui/example-schemas/datastore/school-student-collision.json new file mode 100644 index 00000000..d5c4c40e --- /dev/null +++ b/packages/codegen-ui/example-schemas/datastore/school-student-collision.json @@ -0,0 +1,155 @@ +{ + "models": { + "School": { + "name": "School", + "fields": { + "id": { + "name": "id", + "isArray": false, + "type": "ID", + "isRequired": true, + "attributes": [] + }, + "name": { + "name": "name", + "isArray": false, + "type": "String", + "isRequired": false, + "attributes": [] + }, + "Student": { + "name": "Student", + "isArray": true, + "type": { + "model": "Student" + }, + "isRequired": false, + "attributes": [], + "isArrayNullable": true, + "association": { + "connectionType": "HAS_MANY", + "associatedWith": "schoolID" + } + }, + "createdAt": { + "name": "createdAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + }, + "updatedAt": { + "name": "updatedAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + } + }, + "syncable": true, + "pluralName": "Schools", + "attributes": [ + { + "type": "model", + "properties": {} + }, + { + "type": "auth", + "properties": { + "rules": [ + { + "allow": "public", + "operations": [ + "create", + "update", + "delete", + "read" + ] + } + ] + } + } + ] + }, + "Student": { + "name": "Student", + "fields": { + "id": { + "name": "id", + "isArray": false, + "type": "ID", + "isRequired": true, + "attributes": [] + }, + "name": { + "name": "name", + "isArray": false, + "type": "String", + "isRequired": false, + "attributes": [] + }, + "schoolID": { + "name": "schoolID", + "isArray": false, + "type": "ID", + "isRequired": true, + "attributes": [] + }, + "createdAt": { + "name": "createdAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + }, + "updatedAt": { + "name": "updatedAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + } + }, + "syncable": true, + "pluralName": "Students", + "attributes": [ + { + "type": "model", + "properties": {} + }, + { + "type": "key", + "properties": { + "name": "bySchool", + "fields": [ + "schoolID" + ] + } + }, + { + "type": "auth", + "properties": { + "rules": [ + { + "allow": "public", + "operations": [ + "create", + "update", + "delete", + "read" + ] + } + ] + } + } + ] + } + }, + "enums": {}, + "nonModels": {}, + "version": "5e020d89e4dbb0a2e3b90b771dbcff66" +} \ No newline at end of file diff --git a/packages/codegen-ui/package-lock.json b/packages/codegen-ui/package-lock.json index 75f1d546..55aac98f 100644 --- a/packages/codegen-ui/package-lock.json +++ b/packages/codegen-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aws-amplify/codegen-ui", - "version": "2.13.1", + "version": "2.14.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aws-amplify/codegen-ui", - "version": "2.13.1", + "version": "2.14.2", "license": "Apache-2.0", "dependencies": { "change-case": "^4.1.2", diff --git a/packages/codegen-ui/package.json b/packages/codegen-ui/package.json index 4d58b075..658af593 100644 --- a/packages/codegen-ui/package.json +++ b/packages/codegen-ui/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/codegen-ui", - "version": "2.13.1", + "version": "2.14.2", "description": "generic component code generation interface definitions", "author": "Amazon Web Services", "homepage": "https://docs.amplify.aws/", diff --git a/packages/test-generator/CHANGELOG.md b/packages/test-generator/CHANGELOG.md index b1e5719e..8c12fce3 100644 --- a/packages/test-generator/CHANGELOG.md +++ b/packages/test-generator/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.14.2](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.1...v2.14.2) (2023-06-06) + +**Note:** Version bump only for package @aws-amplify/codegen-ui-test-generator + +## [2.14.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.14.0...v2.14.1) (2023-05-23) + +**Note:** Version bump only for package @aws-amplify/codegen-ui-test-generator + +# [2.14.0](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.14.0) (2023-05-22) + +**Note:** Version bump only for package @aws-amplify/codegen-ui-test-generator + ## [2.13.1](https://github.com/aws-amplify/amplify-codegen-ui/compare/v2.13.0...v2.13.1) (2023-05-02) **Note:** Version bump only for package @aws-amplify/codegen-ui-test-generator diff --git a/packages/test-generator/package-lock.json b/packages/test-generator/package-lock.json index b70cf80d..c300aabd 100644 --- a/packages/test-generator/package-lock.json +++ b/packages/test-generator/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aws-amplify/codegen-ui-test-generator", - "version": "2.13.1", + "version": "2.14.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aws-amplify/codegen-ui-test-generator", - "version": "2.13.1", + "version": "2.14.2", "license": "Apache-2.0", "dependencies": { "@aws-amplify/codegen-ui": "2.12.2", diff --git a/packages/test-generator/package.json b/packages/test-generator/package.json index 8ad8b7b1..b512d8a4 100644 --- a/packages/test-generator/package.json +++ b/packages/test-generator/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/codegen-ui-test-generator", - "version": "2.13.1", + "version": "2.14.2", "description": "Test generator with sample JSON files", "author": "Amazon Web Services", "repository": "https://github.com/aws-amplify/amplify-codegen-ui.git", @@ -19,8 +19,8 @@ "dist/**" ], "dependencies": { - "@aws-amplify/codegen-ui": "2.13.1", - "@aws-amplify/codegen-ui-react": "2.13.1", + "@aws-amplify/codegen-ui": "2.14.2", + "@aws-amplify/codegen-ui-react": "2.14.2", "@types/node": "^15.12.1", "loglevel": "^1.7.1", "typescript": "^4.2.4"