diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45aa7761..e1b63a8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,9 @@ jobs: - run: yarn lint - name: Install Playwright Browsers run: yarn playwright install --with-deps - - run: yarn test-gen && yarn test-app + - run: yarn test-gen + - run: yarn test-next-app + - run: yarn test-react-app - run: yarn test-gen-env - run: yarn test-gen-openapi3 - run: yarn check diff --git a/package.json b/package.json index 6f2b17ba..697edd20 100644 --- a/package.json +++ b/package.json @@ -59,11 +59,12 @@ "eslint-check": "eslint-config-prettier src/index.js", "build": "babel src -d lib --ignore '*.test.js'", "watch": "babel --watch src -d lib --ignore '*.test.js'", - "test-gen": "rm -rf ./tmp && yarn build && ./lib/index.js https://demo.api-platform.com ./tmp/react -g react && ./lib/index.js https://demo.api-platform.com ./tmp/react-native -g react-native && ./lib/index.js https://demo.api-platform.com ./tmp/next -g next && ./lib/index.js https://demo.api-platform.com ./tmp/vue -g vue", + "test-gen": "rm -rf ./tmp && yarn build && ./testgen.sh", + "test-gen-openapi3": "rm -rf ./tmp && yarn build && ENTRYPOINT=https://demo.api-platform.com/docs.json FORMAT=openapi3 ./testgen.sh", "test-gen-custom": "rm -rf ./tmp && yarn build && babel src/generators/ReactGenerator.js src/generators/BaseGenerator.js -d ./tmp/gens && cp -r ./templates/react ./templates/react-common ./templates/entrypoint.js ./tmp/gens && ./lib/index.js https://demo.api-platform.com ./tmp/react-custom -g \"$(pwd)/tmp/gens/ReactGenerator.js\" -t ./tmp/gens", - "test-gen-openapi3": "rm -rf ./tmp && yarn build && ./lib/index.js https://demo.api-platform.com/docs.json ./tmp/react -f openapi3 && ./lib/index.js https://demo.api-platform.com/docs.json ./tmp/react-native -g react-native -f openapi3 && ./lib/index.js https://demo.api-platform.com/docs.json ./tmp/vue -g vue -f openapi3", "test-gen-env": "rm -rf ./tmp && yarn build && API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT=https://demo.api-platform.com API_PLATFORM_CLIENT_GENERATOR_OUTPUT=./tmp ./lib/index.js", - "test-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create next-app --typescript ./tmp/app/next && yarn --cwd ./tmp/app/next add lodash.get lodash.has isomorphic-unfetch formik react-query && yarn --cwd ./tmp/app/next add -D @types/lodash && cp -R ./tmp/next/* ./tmp/app/next && rm ./tmp/app/next/pages/index.tsx && rm -rf ./tmp/app/next/pages/api && yarn --cwd ./tmp/app/next build && start-server-and-test 'yarn --cwd ./tmp/app/next start' http://127.0.0.1:3000/books 'yarn playwright test'" + "test-react-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create react-app ./tmp/app/reactapp && yarn --cwd ./tmp/app/reactapp add react-router-dom@5 redux redux-thunk react-redux redux-form connected-react-router && cp -R ./tmp/react/* ./tmp/app/reactapp/src && cp ./templates/react/index.js ./tmp/app/reactapp/src && start-server-and-test 'BROWSER=none yarn --cwd ./tmp/app/reactapp start' http://127.0.0.1:3000/books/ 'yarn playwright test'", + "test-next-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create next-app --typescript ./tmp/app/next && yarn --cwd ./tmp/app/next add isomorphic-unfetch formik react-query && cp -R ./tmp/next/* ./tmp/app/next && rm ./tmp/app/next/pages/index.tsx && rm -rf ./tmp/app/next/pages/api && yarn --cwd ./tmp/app/next build && start-server-and-test 'yarn --cwd ./tmp/app/next start' http://127.0.0.1:3000/books/ 'yarn playwright test'" }, "lint-staged": { "src/**/*.js": "yarn lint --fix" diff --git a/src/generators/NextGenerator.js b/src/generators/NextGenerator.js index ec9865f1..c023f24c 100644 --- a/src/generators/NextGenerator.js +++ b/src/generators/NextGenerator.js @@ -1,6 +1,6 @@ import chalk from "chalk"; import handlebars from "handlebars"; -import hbh_comparison from "handlebars-helpers/lib/comparison.js"; +import hbhComparison from "handlebars-helpers/lib/comparison.js"; import BaseGenerator from "./BaseGenerator.js"; export default class NextGenerator extends BaseGenerator { @@ -34,7 +34,7 @@ export default class NextGenerator extends BaseGenerator { "utils/mercure.ts", ]); - handlebars.registerHelper("compare", hbh_comparison.compare); + handlebars.registerHelper("compare", hbhComparison.compare); } help(resource) { @@ -59,6 +59,10 @@ export default class NextGenerator extends BaseGenerator { imports, hydraPrefix: this.hydraPrefix, title: resource.title, + hasRelations: fields.some((field) => field.reference || field.embedded), + hasManyRelations: fields.some( + (field) => field.isReferences || field.isEmbeddeds + ), }; // Create directories @@ -134,6 +138,9 @@ export default class NextGenerator extends BaseGenerator { return list; } + const isReferences = field.reference && field.maxCardinality !== 1; + const isEmbeddeds = field.embedded && field.maxCardinality !== 1; + return { ...list, [field.name]: { @@ -141,6 +148,9 @@ export default class NextGenerator extends BaseGenerator { type: this.getType(field), description: this.getDescription(field), readonly: false, + isReferences, + isEmbeddeds, + isRelations: isEmbeddeds || isReferences, }, }; }, {}); diff --git a/src/generators/ReactGenerator.js b/src/generators/ReactGenerator.js index c637ad89..f8828c1a 100644 --- a/src/generators/ReactGenerator.js +++ b/src/generators/ReactGenerator.js @@ -68,18 +68,17 @@ combineReducers({ ${titleLc},/* ... */ }), generate(api, resource, dir) { const lc = resource.title.toLowerCase(); - const titleUcFirst = - resource.title.charAt(0).toUpperCase() + resource.title.slice(1); + const ucf = this.ucFirst(resource.title); const context = { title: resource.title, name: resource.name, lc, uc: resource.title.toUpperCase(), - fields: resource.readableFields, + ucf, + fields: this.parseFields(resource), formFields: this.buildFields(resource.writableFields), hydraPrefix: this.hydraPrefix, - titleUcFirst, }; // Create directories @@ -134,4 +133,40 @@ combineReducers({ ${titleLc},/* ... */ }), this.createEntrypoint(api.entrypoint, `${dir}/config/entrypoint.js`); } + + getDescription(field) { + return field.description ? field.description.replace(/"/g, "'") : ""; + } + + parseFields(resource) { + const fields = [ + ...resource.writableFields, + ...resource.readableFields, + ].reduce((list, field) => { + if (list[field.name]) { + return list; + } + + const isReferences = field.reference && field.maxCardinality !== 1; + const isEmbeddeds = field.embedded && field.maxCardinality !== 1; + + return { + ...list, + [field.name]: { + ...field, + description: this.getDescription(field), + readonly: false, + isReferences, + isEmbeddeds, + isRelations: isEmbeddeds || isReferences, + }, + }; + }, {}); + + return fields; + } + + ucFirst(target) { + return target.charAt(0).toUpperCase() + target.slice(1); + } } diff --git a/templates/next/components/common/ReferenceLinks.tsx b/templates/next/components/common/ReferenceLinks.tsx index e3df4abb..a554da65 100644 --- a/templates/next/components/common/ReferenceLinks.tsx +++ b/templates/next/components/common/ReferenceLinks.tsx @@ -3,12 +3,10 @@ import { Fragment, FunctionComponent } from "react"; interface Props { items: string | string[]; - type: string; useIcon?: boolean; } const ReferenceLinks: FunctionComponent = ({ items, - type, useIcon = false, }) => { if (Array.isArray(items)) { @@ -16,7 +14,7 @@ const ReferenceLinks: FunctionComponent = ({ {items.map((item, index) => (
- +
))}
diff --git a/templates/next/components/foo/Form.tsx b/templates/next/components/foo/Form.tsx index c4272efb..d1ae713b 100644 --- a/templates/next/components/foo/Form.tsx +++ b/templates/next/components/foo/Form.tsx @@ -1,7 +1,7 @@ import { FunctionComponent, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { ErrorMessage, Formik } from "formik"; +import { ErrorMessage{{#if hasManyRelations}}, Field, FieldArray{{/if}}, Formik } from "formik"; import { useMutation } from "react-query"; import { fetch, FetchError, FetchResponse } from "../../utils/dataAccess"; @@ -53,7 +53,20 @@ export const Form: FunctionComponent = ({ {{{lc}}} }) => {

{ {{{lc}}} ? `Edit {{{ucf}}} ${ {{~lc}}['@id']}` : `Create {{{ucf}}}` }

emb['@id']) ?? "", + {{else if embedded}} + {{name}}: {{../lc}}.{{name}}?.['@id'] ?? "", + {{/if}} + {{/each}} + } : + new {{{ucf}}}() + } validate={() => { const errors = {}; // add your validation logic here @@ -100,32 +113,66 @@ export const Form: FunctionComponent = ({ {{{lc}}} }) => {
{{#each formFields}}
- - - + {{#if isRelations}} +
{{name}}
+ ( +
+ {values.{{name}} && values.{{name}}.length > 0 ? ( + values.{{name}}.map((item: any, index: number) => ( +
+ + + +
+ )) + ) : ( + + )} +
+ )} + /> + {{else}} + + + + {{/if}}
- {{/each}} + {{/each}} {status && status.msg && (
= ({ {{{name}}} }) => ( { {{{name}}} && ({{{name}}}.length !== 0) && {{{name}}}.map( ( {{{lc}}} ) => ( {{{lc}}}['@id'] && - + {{#each fields}} {{#if reference}} - + + {{else if isEmbeddeds}} + emb['@id']) } /> + {{else if embedded}} + {{else if (compare type "==" "Date") }} { {{{../lc}}}['{{{name}}}']?.toLocaleString() } {{else}} @@ -40,7 +44,7 @@ export const List: FunctionComponent = ({ {{{name}}} }) => ( {{/if}} {{/each}} - + diff --git a/templates/next/components/foo/Show.tsx b/templates/next/components/foo/Show.tsx index 95c5cdd9..4ef6f964 100644 --- a/templates/next/components/foo/Show.tsx +++ b/templates/next/components/foo/Show.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import Head from "next/head"; -{{#if reference}}import ReferenceLinks from "../common/ReferenceLinks";{{/if}} +{{#if hasRelations}}import ReferenceLinks from "../common/ReferenceLinks";{{/if}} import { fetch } from "../../utils/dataAccess"; import { {{{ucf}}} } from "../../types/{{{ucf}}}"; @@ -32,9 +32,9 @@ export const Show: FunctionComponent = ({ {{{lc}}}, text }) => { return (
- {`Show {{{ucf}}} ${ {{~lc}}['@id']}`} -