Skip to content

Commit

Permalink
Support custom @id column name in scaffold (#8264)
Browse files Browse the repository at this point in the history
Co-authored-by: Dominic Saadi <[email protected]>
Co-authored-by: Tobbe Lundberg <[email protected]>
  • Loading branch information
3 people authored Dec 19, 2023
1 parent 92b9711 commit 505c6fc
Show file tree
Hide file tree
Showing 17 changed files with 211 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ model Tag {
post Post? @relation(fields: [postId], references: [id])
postId Int?
}

model CustomIdField {
uuid String @id @default(uuid())
name String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
globalThis.__dirname = __dirname
import path from 'path'

// Load mocks
import '../../../../lib/test'

import { getDefaultArgs } from '../../../../lib'
import { yargsDefaults as defaults } from '../../helpers'
import * as scaffold from '../scaffold'

jest.mock('execa')

describe('support custom @id name', () => {
let files

beforeAll(async () => {
files = await scaffold.files({
...getDefaultArgs(defaults),
typescript: true,
model: 'CustomIdField',
tests: true,
nestScaffoldByModel: true,
})
})

test('creates routes with the custom id name', async () => {
const customIdFieldRoutes = await scaffold.routes({
model: 'CustomIdField',
nestScaffoldByModel: true,
})
expect(customIdFieldRoutes).toEqual([
'<Route path="/custom-id-fields/new" page={CustomIdFieldNewCustomIdFieldPage} name="newCustomIdField" />',
'<Route path="/custom-id-fields/{uuid}/edit" page={CustomIdFieldEditCustomIdFieldPage} name="editCustomIdField" />',
'<Route path="/custom-id-fields/{uuid}" page={CustomIdFieldCustomIdFieldPage} name="customIdField" />',
'<Route path="/custom-id-fields" page={CustomIdFieldCustomIdFieldsPage} name="customIdFields" />',
])
})

test('creates a cell with the custom id name', () => {
const customIdFieldCellPath =
'/path/to/project/web/src/components/CustomIdField/CustomIdFieldCell/CustomIdFieldCell.tsx'

const cell = files[path.normalize(customIdFieldCellPath)]
expect(cell).toContain('FindCustomIdFieldByUuid($uuid: String!)')
expect(cell).toContain('customIdField: customIdField(uuid: $uuid)')
})

test('creates an edit cell with the custom id name', () => {
const customIdFieldEditCellPath =
'/path/to/project/web/src/components/CustomIdField/EditCustomIdFieldCell/EditCustomIdFieldCell.tsx'

const cell = files[path.normalize(customIdFieldEditCellPath)]
expect(cell).toContain('query EditCustomIdFieldByUuid($uuid: String!)')
})

test('creates a component with the custom id name', () => {
const customIdFieldComponentPath =
'/path/to/project/web/src/components/CustomIdField/CustomIdField/CustomIdField.tsx'

const cell = files[path.normalize(customIdFieldComponentPath)]
expect(cell).toContain('DeleteCustomIdFieldMutation($uuid: String!)')
expect(cell).toContain('deleteCustomIdField(uuid: $uuid)')
expect(cell).toContain('deleteCustomIdField({ variables: { uuid } })')
})

test('creates a form with the custom id name', () => {
const customIdFieldFormPath =
'/path/to/project/web/src/components/CustomIdField/CustomIdFieldForm/CustomIdFieldForm.tsx'

const cell = files[path.normalize(customIdFieldFormPath)]
expect(cell).toContain('props.onSave(data, props?.customIdField?.uuid)')
})

test('creates a sdl with the custom id name', () => {
const customIdFieldSdlPath =
'/path/to/project/api/src/graphql/customIdFields.sdl.ts'

const sdl = files[path.normalize(customIdFieldSdlPath)]
const match = sdl.match(/uuid: String!/g)
expect(match).toHaveLength(4)
})

test('creates a service with the custom id name', () => {
const customIdFieldServicePath =
'/path/to/project/api/src/graphql/customIdFields.sdl.ts'

const sdl = files[path.normalize(customIdFieldServicePath)]
const match = sdl.match(/uuid: String!/g)
expect(match).toHaveLength(4)
})
})
15 changes: 13 additions & 2 deletions packages/cli/src/commands/generate/scaffold/scaffold.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ const getIdType = (model) => {
return model.fields.find((field) => field.isId)?.type
}

const getIdName = (model) => {
return model.fields.find((field) => field.isId)?.name
}

const filterAutoGeneratedColumnsForScaffold = (column) => {
const autoGeneratedFunctions = ['now', 'autoincrement']
return !(
Expand Down Expand Up @@ -495,6 +499,7 @@ const pageFiles = async (
const model = await getSchema(singularName)
const idType = getIdType(model)
const idTsType = mapPrismaScalarToPagePropTsType(idType)
const idName = getIdName(model)

let fileList = {}

Expand Down Expand Up @@ -531,6 +536,7 @@ const pageFiles = async (
}),
{
idTsType,
idName,
name,
pascalScaffoldPath,
...templateStrings,
Expand All @@ -557,6 +563,8 @@ const componentFiles = async (
const singularName = pascalcase(singularize(name))
const model = await getSchema(singularName)
const idType = getIdType(model)
const idName = getIdName(model)
const pascalIdName = pascalcase(idName)
const intForeignKeys = intForeignKeysForModel(model)
let fileList = {}

Expand Down Expand Up @@ -594,6 +602,8 @@ const componentFiles = async (
{
name,
idType,
idName,
pascalIdName,
intForeignKeys,
pascalScaffoldPath,
...templateStrings,
Expand Down Expand Up @@ -623,6 +633,7 @@ export const routes = async ({
const nameVars = nameVariants(name)
const model = await getSchema(nameVars.singularPascalName)
const idRouteParam = getIdType(model) === 'Int' ? ':Int' : ''
const idName = getIdName(model)

const paramScaffoldPath =
scaffoldPath === ''
Expand All @@ -638,9 +649,9 @@ export const routes = async ({
// new
`<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}/new" page={${pageRoot}New${nameVars.singularPascalName}Page} name="${templateNames.newRouteName}" />`,
// edit
`<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}/{id${idRouteParam}}/edit" page={${pageRoot}Edit${nameVars.singularPascalName}Page} name="${templateNames.editRouteName}" />`,
`<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}/{${idName}${idRouteParam}}/edit" page={${pageRoot}Edit${nameVars.singularPascalName}Page} name="${templateNames.editRouteName}" />`,
// singular
`<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}/{id${idRouteParam}}" page={${pageRoot}${nameVars.singularPascalName}Page} name="${templateNames.singularRouteName}" />`,
`<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}/{${idName}${idRouteParam}}" page={${pageRoot}${nameVars.singularPascalName}Page} name="${templateNames.singularRouteName}" />`,
// plural
`<Route path="/${paramScaffoldPath}${nameVars.pluralParamName}" page={${pageRoot}${nameVars.pluralPascalName}Page} name="${templateNames.pluralRouteName}" />`,
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Edit${singularPascalName}ById, Update${singularPascalName}Input } from 'types/graphql'
import type { Edit${singularPascalName}By${pascalIdName}, Update${singularPascalName}Input } from 'types/graphql'

import { navigate, routes } from '@redwoodjs/router'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
Expand All @@ -8,15 +8,15 @@ import { toast } from '@redwoodjs/web/toast'
import ${singularPascalName}Form from '${importComponentNameForm}'

export const QUERY = gql`
query Edit${singularPascalName}ById($id: ${idType}!) {
${singularCamelName}: ${singularCamelName}(id: $id) {<% columns.forEach(column => { %>
query Edit${singularPascalName}By${pascalIdName}($${idName}: ${idType}!) {
${singularCamelName}: ${singularCamelName}(${idName}: $${idName}) {<% columns.forEach(column => { %>
<%= column.name %><% }) %>
}
}
`
const UPDATE_${singularConstantName}_MUTATION = gql`
mutation Update${singularPascalName}Mutation($id: ${idType}!, $input: Update${singularPascalName}Input!) {
update${singularPascalName}(id: $id, input: $input) {<% columns.forEach(column => { %>
mutation Update${singularPascalName}Mutation($${idName}: ${idType}!, $input: Update${singularPascalName}Input!) {
update${singularPascalName}(${idName}: $${idName}, input: $input) {<% columns.forEach(column => { %>
<%= column.name %><% }) %>
}
}
Expand All @@ -28,7 +28,7 @@ export const Failure = ({ error }: CellFailureProps) => (
<div className="rw-cell-error">{error?.message}</div>
)

export const Success = ({ ${singularCamelName} }: CellSuccessProps<Edit${singularPascalName}ById>) => {
export const Success = ({ ${singularCamelName} }: CellSuccessProps<Edit${singularPascalName}By${pascalIdName}>) => {
const [update${singularPascalName}, { loading, error }] = useMutation(
UPDATE_${singularConstantName}_MUTATION,
{
Expand All @@ -44,7 +44,7 @@ export const Success = ({ ${singularCamelName} }: CellSuccessProps<Edit${singula

const onSave = (
input: Update${singularPascalName}Input,
id: Edit${singularPascalName}ById['${singularCamelName}']['id']
id: Edit${singularPascalName}By${pascalIdName}['${singularCamelName}']['id']
) => {
update${singularPascalName}({ variables: { id, input } })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import { toast } from '@redwoodjs/web/toast'

import { ${formattersImports} } from 'src/lib/formatters'

import type { Delete${singularPascalName}MutationVariables, Find${singularPascalName}ById } from 'types/graphql'
import type { Delete${singularPascalName}MutationVariables, Find${singularPascalName}By${pascalIdName} } from 'types/graphql'

const DELETE_${singularConstantName}_MUTATION = gql`
mutation Delete${singularPascalName}Mutation($id: ${idType}!) {
delete${singularPascalName}(id: $id) {
id
mutation Delete${singularPascalName}Mutation($${idName}: ${idType}!) {
delete${singularPascalName}(${idName}: $${idName}) {
${idName}
}
}
`

interface Props {
${singularCamelName}: NonNullable<Find${singularPascalName}ById['${singularCamelName}']>
${singularCamelName}: NonNullable<Find${singularPascalName}By${pascalIdName}['${singularCamelName}']>
}

const ${singularPascalName} = ({ ${singularCamelName} }: Props) => {
Expand All @@ -30,9 +30,9 @@ const ${singularPascalName} = ({ ${singularCamelName} }: Props) => {
},
})

const onDeleteClick = (id: Delete${singularPascalName}MutationVariables['id']) => {
if (confirm('Are you sure you want to delete ${singularCamelName} ' + id + '?')) {
delete${singularPascalName}({ variables: { id } })
const onDeleteClick = (${idName}: Delete${singularPascalName}MutationVariables['${idName}']) => {
if (confirm('Are you sure you want to delete ${singularCamelName} ' + ${idName} + '?')) {
delete${singularPascalName}({ variables: { ${idName} } })
}
}

Expand All @@ -41,7 +41,7 @@ const ${singularPascalName} = ({ ${singularCamelName} }: Props) => {
<div className="rw-segment">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">
${singularPascalName} {${singularCamelName}.id} Detail
${singularPascalName} {${singularCamelName}.${idName}} Detail
</h2>
</header>
<table className="rw-table">
Expand All @@ -59,15 +59,15 @@ const ${singularPascalName} = ({ ${singularCamelName} }: Props) => {
</div>
<nav className="rw-button-group">
<Link
to={routes.${editRouteName}({ id: ${singularCamelName}.id })}
to={routes.${editRouteName}({ ${idName}: ${singularCamelName}.${idName} })}
className="rw-button rw-button-blue"
>
Edit
</Link>
<button
type="button"
className="rw-button rw-button-red"
onClick={() => onDeleteClick(${singularCamelName}.id)}
onClick={() => onDeleteClick(${singularCamelName}.${idName})}
>
Delete
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Find${singularPascalName}ById } from 'types/graphql'
import type { Find${singularPascalName}By${pascalIdName} } from 'types/graphql'

import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'

import ${singularPascalName} from '${importComponentName}'

export const QUERY = gql`
query Find${singularPascalName}ById($id: ${idType}!) {
${singularCamelName}: ${singularCamelName}(id: $id) {<% columns.forEach(column => { %>
query Find${singularPascalName}By${pascalIdName}($${idName}: ${idType}!) {
${singularCamelName}: ${singularCamelName}(${idName}: $${idName}) {<% columns.forEach(column => { %>
<%= column.name %><% }) %>
}
}
Expand All @@ -20,6 +20,6 @@ export const Failure = ({ error }: CellFailureProps) => (
<div className="rw-cell-error">{error?.message}</div>
)

export const Success = ({ ${singularCamelName} }: CellSuccessProps<Find${singularPascalName}ById>) => {
export const Success = ({ ${singularCamelName} }: CellSuccessProps<Find${singularPascalName}By${pascalIdName}>) => {
return <${singularPascalName} ${singularCamelName}={${singularCamelName}} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Submit,
} from '@redwoodjs/forms'

import type { Edit${singularPascalName}ById, Update${singularPascalName}Input } from 'types/graphql'
import type { Edit${singularPascalName}By${pascalIdName}, Update${singularPascalName}Input } from 'types/graphql'
import type { RWGqlError } from '@redwoodjs/forms'


Expand All @@ -19,11 +19,11 @@ const formatDatetime = (value) => {
}
<% } %>

type Form${singularPascalName} = NonNullable<Edit${singularPascalName}ById['${singularCamelName}']>
type Form${singularPascalName} = NonNullable<Edit${singularPascalName}By${pascalIdName}['${singularCamelName}']>

interface ${singularPascalName}FormProps {
${singularCamelName}?: Edit${singularPascalName}ById['${singularCamelName}']
onSave: (data: Update${singularPascalName}Input, id?: Form${singularPascalName}['id']) => void
${singularCamelName}?: Edit${singularPascalName}By${pascalIdName}['${singularCamelName}']
onSave: (data: Update${singularPascalName}Input, ${idName}?: Form${singularPascalName}['${idName}']) => void
error: RWGqlError
loading: boolean
}
Expand All @@ -42,7 +42,7 @@ const ${singularPascalName}Form = (props: ${singularPascalName}FormProps) => {
}
<% } %>
<% }) %>
props.onSave(data, props?.${singularCamelName}?.id)
props.onSave(data, props?.${singularCamelName}?.${idName})
}

return (
Expand All @@ -66,7 +66,7 @@ const ${singularPascalName}Form = (props: ${singularPascalName}FormProps) => {
<% if (!column.isRequired) { %>
<div className="rw-check-radio-items">
<${column.component}
id="${singularCamelName}-${column.name}-none"
${idName}="${singularCamelName}-${column.name}-none"
name="${column.name}"
defaultValue=""
${column.defaultProp}={!props.spot?.spotType}
Expand All @@ -83,7 +83,7 @@ const ${singularPascalName}Form = (props: ${singularPascalName}FormProps) => {
%>
<div className="rw-check-radio-items">
<${column.component}
id="${singularCamelName}-${column.name}-${i}"
${idName}="${singularCamelName}-${column.name}-${i}"
name="${columnComponentName}"
defaultValue="${value.name}"
${column.defaultProp}={props.${singularCamelName}?.${column.name}?.includes('${value.name}')}
Expand Down
Loading

0 comments on commit 505c6fc

Please sign in to comment.