Skip to content
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
5 changes: 5 additions & 0 deletions packages/graphql/src/schema/fieldToSchemaMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
})

if (Object.keys(objectType.getFields()).length) {
// Store block slug in extensions for use in select building
objectType.extensions = {
...objectType.extensions,
blockSlug: block.slug,
}
graphqlResult.types.blockTypes[block.slug] = objectType
}
}
Expand Down
33 changes: 22 additions & 11 deletions packages/graphql/src/utilities/select.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GraphQLObjectType, GraphQLResolveInfo, SelectionSetNode} from 'graphql'
import type { GraphQLObjectType, GraphQLResolveInfo, SelectionSetNode } from 'graphql'
import type { FieldBase, JoinField, RelationshipField, TypedCollectionSelect } from 'payload'

import { getNamedType, isInterfaceType, isObjectType, isUnionType, Kind } from 'graphql'
Expand All @@ -18,7 +18,9 @@ export function resolveSelect(info: GraphQLResolveInfo, select: SelectType): Sel
const pathType = info.schema.getType(path.typename) as GraphQLObjectType

if (pathType) {
const field = pathType?.getFields()?.[pathKey]?.extensions?.field as JoinField | RelationshipField
const field = pathType?.getFields()?.[pathKey]?.extensions?.field as
| JoinField
| RelationshipField

if (field?.type === 'join') {
path = path.prev
Expand All @@ -39,7 +41,9 @@ export function resolveSelect(info: GraphQLResolveInfo, select: SelectType): Sel
}

traverseTree(info.path)
traversePath.forEach(key => { select = select?.[key] as SelectType })
traversePath.forEach((key) => {
select = select?.[key] as SelectType
})
}

return select
Expand All @@ -49,14 +53,14 @@ function buildSelect(info: GraphQLResolveInfo) {
const returnType = getNamedType(info.returnType) as GraphQLObjectType
const selectionSet = info.fieldNodes[0].selectionSet

if (!returnType) return
if (!returnType) {return}

return buildSelectTree(info, selectionSet, returnType)
}
function buildSelectTree(
info: GraphQLResolveInfo,
selectionSet: SelectionSetNode,
type: GraphQLObjectType
type: GraphQLObjectType,
): SelectType {
const fieldMap = type.getFields?.()
const fieldTree: SelectType = {}
Expand All @@ -70,8 +74,8 @@ function buildSelectTree(
const field = fieldSchema?.extensions?.field as FieldBase
const fieldNameOriginal = field?.name || fieldName

if (fieldName === '__typename') continue
if (fieldSchema == undefined) continue
if (fieldName === '__typename') {continue}
if (fieldSchema == undefined) {continue}

if (selection.selectionSet) {
const type = getNamedType(fieldSchema.type) as GraphQLObjectType
Expand All @@ -89,7 +93,8 @@ function buildSelectTree(
case Kind.FRAGMENT_SPREAD: {
const fragmentName = selection.name.value
const fragment = info.fragments[fragmentName]
const fragmentType = fragment && info.schema.getType(fragment.typeCondition.name.value) as GraphQLObjectType
const fragmentType =
fragment && (info.schema.getType(fragment.typeCondition.name.value) as GraphQLObjectType)

if (fragmentType) {
Object.assign(fieldTree, buildSelectTree(info, fragment.selectionSet, fragmentType))
Expand All @@ -99,12 +104,18 @@ function buildSelectTree(

case Kind.INLINE_FRAGMENT: {
const fragmentType = selection.typeCondition
? info.schema.getType(selection.typeCondition.name.value) as GraphQLObjectType
? (info.schema.getType(selection.typeCondition.name.value) as GraphQLObjectType)
: type


if (fragmentType) {
Object.assign(fieldTree, buildSelectTree(info, selection.selectionSet, fragmentType))
// Block types in unions need selections nested under their slug
const blockSlug = fragmentType.extensions?.blockSlug as string | undefined

if (blockSlug && isUnionType(type)) {
fieldTree[blockSlug] = buildSelectTree(info, selection.selectionSet, fragmentType)
} else {
Object.assign(fieldTree, buildSelectTree(info, selection.selectionSet, fragmentType))
}
}
break
}
Expand Down
11 changes: 11 additions & 0 deletions test/graphql/blocks/ContentBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Block } from 'payload'

export const ContentBlock: Block = {
slug: 'content',
fields: [
{
name: 'text',
type: 'text',
},
],
}
6 changes: 6 additions & 0 deletions test/graphql/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'path'

import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import { ContentBlock } from './blocks/ContentBlock.js'

const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
Expand Down Expand Up @@ -31,6 +32,11 @@ export default buildConfigWithDefaults({
complexity: 801,
},
},
{
name: 'contentBlockField',
type: 'blocks',
blocks: [ContentBlock],
},
],
},
],
Expand Down
69 changes: 69 additions & 0 deletions test/graphql/int.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,74 @@ query {
.then((res) => res.json())
expect(res_2.errors).toBeFalsy()
})

it('should handle blocks with select: true', async () => {
const createdPost = await payload.create({
collection: 'posts',
data: {
title: 'Test Post with Blocks',
contentBlockField: [
{
blockType: 'content',
text: 'Hello World from Block',
},
],
},
})

// Query without select: true
const queryWithoutSelect = `query {
Post(id: ${idToString(createdPost.id, payload)}) {
title
contentBlockField {
... on Content {
text
}
}
}
}`

const responseWithoutSelect = await restClient
.GRAPHQL_POST({ body: JSON.stringify({ query: queryWithoutSelect }) })
.then((res) => res.json())

expect(responseWithoutSelect.errors).toBeFalsy()
expect(responseWithoutSelect.data.Post.title).toBe('Test Post with Blocks')
expect(responseWithoutSelect.data.Post.contentBlockField).toHaveLength(1)
expect(responseWithoutSelect.data.Post.contentBlockField[0].text).toBe(
'Hello World from Block',
)

// Query with select: true
const queryWithSelect = `query {
Posts(select: true, where: { id: { equals: ${idToString(createdPost.id, payload)} } }) {
docs {
title
contentBlockField {
... on Content {
text
}
}
}
}
}`

const responseWithSelect = await restClient
.GRAPHQL_POST({ body: JSON.stringify({ query: queryWithSelect }) })
.then((res) => res.json())

expect(responseWithSelect.errors).toBeFalsy()
expect(responseWithSelect.data.Posts.docs).toHaveLength(1)
expect(responseWithSelect.data.Posts.docs[0].title).toBe('Test Post with Blocks')
expect(responseWithSelect.data.Posts.docs[0].contentBlockField).toHaveLength(1)
expect(responseWithSelect.data.Posts.docs[0].contentBlockField[0].text).toBe(
'Hello World from Block',
)

await payload.delete({
collection: 'posts',
id: createdPost.id,
})
})
})
})