Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby): print childOf directive for implicit child fields #28483

Merged
merged 7 commits into from
Dec 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions packages/gatsby/src/schema/__tests__/__snapshots__/print.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ input Baz {
qux: Boolean
}

type BarChild implements Node @childOf(types: [\\"Test\\"]) @dontInfer {
bar: String
}

type FooChild implements Node @childOf(types: [\\"Test\\"]) @dontInfer {
bar: String
}

type Test implements Node @dontInfer {
foo: Int
}"
Expand Down Expand Up @@ -148,6 +156,10 @@ type Nested {

input Baz {
qux: Boolean
}

type BarChild implements Node @childOf(types: [\\"Test\\"]) @dontInfer {
bar: String
}"
`;

Expand Down Expand Up @@ -266,6 +278,14 @@ type OneMoreTest implements Node @dontInfer {

union ThisOrThat = AnotherTest | OneMoreTest

type BarChild implements Node @childOf(types: [\\"Test\\"]) @dontInfer {
bar: String
}

type FooChild implements Node @childOf(types: [\\"Test\\"]) @dontInfer {
bar: String
}

type Test implements Node @dontInfer {
foo: Int
}"
Expand Down Expand Up @@ -423,6 +443,14 @@ input Baz {
qux: Boolean
}

type BarChild implements Node @childOf(types: [\\"Test\\"]) @dontInfer {
bar: String
}

type FooChild implements Node @childOf(types: [\\"Test\\"]) @dontInfer {
bar: String
}

type Test implements Node @dontInfer {
foo: Int
}"
Expand Down
44 changes: 42 additions & 2 deletions packages/gatsby/src/schema/__tests__/print.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const { build } = require(`..`)
import { buildObjectType } from "../types/type-builders"
const { store } = require(`../../redux`)
const { actions } = require(`../../redux/actions/restricted`)
const { actions: publicActions } = require(`../../redux/actions/public`)
const { createParentChildLink } = publicActions
const { printTypeDefinitions } = actions

jest.mock(`fs-extra`)
Expand Down Expand Up @@ -41,14 +43,35 @@ jest.spyOn(global.Date.prototype, `toISOString`).mockReturnValue(`2019-01-01`)
describe(`Print type definitions`, () => {
beforeEach(() => {
store.dispatch({ type: `DELETE_CACHE` })
const node = {
const node1 = {
id: `test1`,
internal: {
type: `Test`,
},
children: [],
foo: 26,
}
store.dispatch({ type: `CREATE_NODE`, payload: { ...node } })
const node2 = {
id: `test2`,
parent: `test1`,
internal: {
type: `FooChild`,
},
bar: `bar`,
}
const node3 = {
id: `test3`,
parent: `test1`,
internal: {
type: `BarChild`,
},
bar: `bar`,
}
store.dispatch({ type: `CREATE_NODE`, payload: { ...node1 } })
store.dispatch({ type: `CREATE_NODE`, payload: { ...node2 } })
store.dispatch({ type: `CREATE_NODE`, payload: { ...node3 } })
createParentChildLink({ parent: node1, child: node2 })
createParentChildLink({ parent: node1, child: node3 })
const typeDefs = []
typeDefs.push(`
type AnotherTest implements Node & ITest {
Expand Down Expand Up @@ -96,6 +119,18 @@ describe(`Print type definitions`, () => {
types: [`OneMoreTest`],
},
},
}),
buildObjectType({
name: `BarChild`,
fields: {
id: `ID!`,
},
interfaces: [`Node`],
extensions: {
childOf: {
types: [`Test`],
},
},
})
)
store.dispatch({
Expand All @@ -108,6 +143,11 @@ describe(`Print type definitions`, () => {
payload: typeDefs[1],
plugin: { name: `gatsby-plugin-another-test` },
})
store.dispatch({
type: `CREATE_TYPES`,
payload: typeDefs[2],
plugin: { name: `gatsby-plugin-another-test` },
})
})

it(`saves type definitions to default file`, async () => {
Expand Down
18 changes: 18 additions & 0 deletions packages/gatsby/src/schema/extensions/__tests__/child-relations.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,24 @@ describe(`Define parent-child relationships with field extensions`, () => {
)
})

it(`does not show deprecation warning for inferred child fields`, async () => {
dispatch(
createTypes(`
type Parent implements Node @dontInfer @mimeTypes(types: ["application/listenup"]) {
id: ID!
}
type Child implements Node @childOf(mimeTypes: ["application/listenup"]) {
id: ID!
}
type AnotherChild implements Node @childOf(types: ["Parent"]) {
id: ID!
}
`)
)
await buildSchema()
expect(report.warn).toBeCalledTimes(0)
})

it(`adds children[Field] field to parent type with childOf extension`, async () => {
dispatch(
createTypes(`
Expand Down
116 changes: 74 additions & 42 deletions packages/gatsby/src/schema/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ const updateSchemaComposer = async ({
inferenceMetadata,
parentSpan: activity.span,
})
addInferredChildOfExtensions({
schemaComposer,
})
activity.end()

activity = report.phantomActivity(`Processing types`, {
Expand Down Expand Up @@ -202,11 +205,6 @@ const processTypeComposer = async ({

if (typeComposer.hasInterface(`Node`)) {
await addNodeInterfaceFields({ schemaComposer, typeComposer, parentSpan })
await addImplicitConvenienceChildrenFields({
schemaComposer,
typeComposer,
parentSpan,
})
}
await determineSearchableFields({
schemaComposer,
Expand Down Expand Up @@ -999,15 +997,42 @@ const addConvenienceChildrenFields = ({ schemaComposer }) => {
})
}

const addImplicitConvenienceChildrenFields = ({
schemaComposer,
typeComposer,
}) => {
const isExplicitChild = ({ typeComposer, childTypeComposer }) => {
if (!childTypeComposer.hasExtension(`childOf`)) {
return false
}
const childOfExtension = childTypeComposer.getExtension(`childOf`)
const { types: parentMimeTypes = [] } =
typeComposer.getExtension(`mimeTypes`) ?? {}

return (
childOfExtension?.types?.includes(typeComposer.getTypeName()) ||
childOfExtension?.mimeTypes?.some(mimeType =>
parentMimeTypes.includes(mimeType)
)
)
}

const addInferredChildOfExtensions = ({ schemaComposer }) => {
schemaComposer.forEach(typeComposer => {
if (
typeComposer instanceof ObjectTypeComposer &&
typeComposer.hasInterface(`Node`)
) {
addInferredChildOfExtension({
schemaComposer,
typeComposer,
})
}
})
}

const addInferredChildOfExtension = ({ schemaComposer, typeComposer }) => {
const shouldInfer = typeComposer.getExtension(`infer`)
// In Gatsby v3, when `@dontInfer` is set, children fields will not be
// created for parent-child relations set by plugins with
// In Gatsby v3, when `@dontInfer` is set, `@childOf` extension will not be
// automatically created for parent-child relations set by plugins with
// `createParentChildLink`. With `@dontInfer`, only parent-child
// relations explicitly set with the `childOf` extension will be added.
// relations explicitly set with the `@childOf` extension will be added.
// if (shouldInfer === false) return

const parentTypeName = typeComposer.getTypeName()
Expand All @@ -1016,39 +1041,46 @@ const addImplicitConvenienceChildrenFields = ({
const childNodesByType = groupChildNodesByType({ nodes })

Object.keys(childNodesByType).forEach(typeName => {
// Adding children fields to types with the `@dontInfer` extension is deprecated
if (shouldInfer === false) {
const childTypeComposer = schemaComposer.getAnyTC(typeName)
const childOfExtension = childTypeComposer.getExtension(`childOf`)
const childTypeComposer = schemaComposer.getAnyTC(typeName)
let childOfExtension = childTypeComposer.getExtension(`childOf`)

// Only warn when the parent-child relation has not been explicitly set with
if (
!childOfExtension ||
!childOfExtension.types.includes(parentTypeName)
) {
const childField = fieldNames.convenienceChild(typeName)
const childrenField = fieldNames.convenienceChildren(typeName)
const childOfTypes = (childOfExtension?.types ?? [])
.concat(parentTypeName)
.map(name => `"${name}"`)
.join(`,`)

report.warn(
`Deprecation warning: ` +
`In Gatsby v3 fields \`${parentTypeName}.${childField}\` and \`${parentTypeName}.${childrenField}\` ` +
`will not be added automatically because ` +
`type \`${typeName}\` does not explicitly list type \`${parentTypeName}\` in \`childOf\` extension.\n` +
`Add the following type definition to fix this:\n\n` +
` type ${typeName} implements Node @childOf(types: [${childOfTypes}]) {\n` +
` id: ID!\n` +
` }\n\n` +
`https://www.gatsbyjs.com/docs/actions/#createTypes`
)
}
if (isExplicitChild({ typeComposer, childTypeComposer })) {
return
}
if (shouldInfer === false) {
// Adding children fields to types with the `@dontInfer` extension is deprecated
// Only warn when the parent-child relation has not been explicitly set with `childOf` directive
const childField = fieldNames.convenienceChild(typeName)
const childrenField = fieldNames.convenienceChildren(typeName)
const childOfTypes = (childOfExtension?.types ?? [])
.concat(parentTypeName)
.map(name => `"${name}"`)
.join(`,`)

typeComposer.addFields(createChildrenField(typeName))
typeComposer.addFields(createChildField(typeName))
report.warn(
`Deprecation warning: ` +
`In Gatsby v3 fields \`${parentTypeName}.${childField}\` and \`${parentTypeName}.${childrenField}\` ` +
`will not be added automatically because ` +
`type \`${typeName}\` does not explicitly list type \`${parentTypeName}\` in \`childOf\` extension.\n` +
`Add the following type definition to fix this:\n\n` +
` type ${typeName} implements Node @childOf(types: [${childOfTypes}]) {\n` +
` id: ID!\n` +
` }\n\n` +
`https://www.gatsbyjs.com/docs/actions/#createTypes`
)
}
// Set `@childOf` extension automatically
// This will cause convenience children fields like `childImageSharp`
// to be added in `addConvenienceChildrenFields` method.
// Also required for proper printing of the `@childOf` directive in the snapshot plugin
if (!childOfExtension) {
childOfExtension = {}
}
if (!childOfExtension.types) {
childOfExtension.types = []
}
childOfExtension.types.push(parentTypeName)
childTypeComposer.setExtension(`childOf`, childOfExtension)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The essence of this PR: instead of adding fields directly we add the childOf directive here. And fields are always added in a single place now - where this directive is processed (in addConvenienceChildrenFields):

children.forEach(child => {
typeComposer.addFields(createChildrenField(child.getTypeName()))
typeComposer.addFields(createChildField(child.getTypeName()))
})

})
}

Expand Down