fix(graphql): graphql tab schema not handling tabs correctly (#13850)

Fixes https://github.com/payloadcms/payload/issues/13833

When generating graphql schemas, named tabs were not properly being
accounted for. This PR fixes that and ensure that the correct schema
types are generated for named tabs.
This commit is contained in:
Jarrod Flesch
2025-09-18 12:34:50 -04:00
committed by GitHub
parent 42b5935772
commit 83b6e3757d
4 changed files with 356 additions and 55 deletions

21
.vscode/launch.json vendored
View File

@@ -269,6 +269,27 @@
"outputCapture": "std",
"request": "launch",
"type": "node-terminal"
},
{
"name": "Debug GraphQL Schema Generation",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/test/generateGraphQLSchema.ts",
"args": ["graphql"],
"cwd": "${workspaceFolder}",
"env": {
"NODE_OPTIONS": "--no-deprecation --no-experimental-strip-types"
},
"runtimeArgs": [
"--no-deprecation",
"--no-experimental-strip-types",
"--import",
"@swc-node/register/esm-register"
],
"console": "integratedTerminal",
"outputCapture": "std",
"sourceMaps": true,
"skipFiles": ["<node_internals>/**"]
}
],
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387

View File

@@ -69,7 +69,7 @@ const buildFields = (label, fieldsToBuild) =>
}
}
if (!field.name && field.fields) {
if (!field.name && field.fields && field.fields.length) {
const subFields = buildFields(label, field.fields)
return {
@@ -81,6 +81,18 @@ const buildFields = (label, fieldsToBuild) =>
if (field.type === 'tabs') {
return field.tabs.reduce(
(fieldsWithTabFields, tab) => {
if ('name' in tab) {
if (tab.fields.length) {
const tabName = formatName(tab.name)
fieldsWithTabFields[tabName] = {
type: new GraphQLObjectType({
name: `${label}_${tabName}`,
fields: buildFields(`${label}_${tabName}`, tab.fields),
}),
}
}
return fieldsWithTabFields
}
return {
...fieldsWithTabFields,
...buildFields(label, tab.fields),

View File

@@ -22,7 +22,14 @@ export const recursivelyBuildNestedPaths = ({ field, nestedFieldName2, parentNam
...recursivelyBuildNestedPaths({
field: {
...tab,
type: 'name' in tab ? 'group' : 'row',
...('name' in tab
? {
name: `${nestedFieldName ? `${nestedFieldName}__` : ''}${tab.name}`,
type: 'group',
}
: {
type: 'row',
}),
},
nestedFieldName2: nestedFieldName,
parentName,

View File

@@ -1,21 +1,21 @@
type Query {
Post(id: String!, draft: Boolean): Post
Posts(draft: Boolean, where: Post_where, limit: Int, page: Int, pagination: Boolean, sort: String): Posts
countPosts(draft: Boolean, where: Post_where): countPosts
Post(id: String!, draft: Boolean, trash: Boolean): Post
Posts(draft: Boolean, where: Post_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): Posts
countPosts(draft: Boolean, trash: Boolean, where: Post_where): countPosts
docAccessPost(id: String!): postsDocAccess
User(id: String!, draft: Boolean): User
Users(draft: Boolean, where: User_where, limit: Int, page: Int, pagination: Boolean, sort: String): Users
countUsers(draft: Boolean, where: User_where): countUsers
User(id: String!, draft: Boolean, trash: Boolean): User
Users(draft: Boolean, where: User_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): Users
countUsers(draft: Boolean, trash: Boolean, where: User_where): countUsers
docAccessUser(id: String!): usersDocAccess
meUser: usersMe
initializedUser: Boolean
PayloadLockedDocument(id: String!, draft: Boolean): PayloadLockedDocument
PayloadLockedDocuments(draft: Boolean, where: PayloadLockedDocument_where, limit: Int, page: Int, pagination: Boolean, sort: String): PayloadLockedDocuments
countPayloadLockedDocuments(draft: Boolean, where: PayloadLockedDocument_where): countPayloadLockedDocuments
PayloadLockedDocument(id: String!, draft: Boolean, trash: Boolean): PayloadLockedDocument
PayloadLockedDocuments(draft: Boolean, where: PayloadLockedDocument_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): PayloadLockedDocuments
countPayloadLockedDocuments(draft: Boolean, trash: Boolean, where: PayloadLockedDocument_where): countPayloadLockedDocuments
docAccessPayloadLockedDocument(id: String!): payload_locked_documentsDocAccess
PayloadPreference(id: String!, draft: Boolean): PayloadPreference
PayloadPreferences(draft: Boolean, where: PayloadPreference_where, limit: Int, page: Int, pagination: Boolean, sort: String): PayloadPreferences
countPayloadPreferences(draft: Boolean, where: PayloadPreference_where): countPayloadPreferences
PayloadPreference(id: String!, draft: Boolean, trash: Boolean): PayloadPreference
PayloadPreferences(draft: Boolean, where: PayloadPreference_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): PayloadPreferences
countPayloadPreferences(draft: Boolean, trash: Boolean, where: PayloadPreference_where): countPayloadPreferences
docAccessPayloadPreference(id: String!): payload_preferencesDocAccess
Access: Access
}
@@ -35,17 +35,17 @@ A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `dat
scalar DateTime
type Posts {
docs: [Post]
hasNextPage: Boolean
hasPrevPage: Boolean
limit: Int
docs: [Post!]!
hasNextPage: Boolean!
hasPrevPage: Boolean!
limit: Int!
nextPage: Int
offset: Int
page: Int
pagingCounter: Int
page: Int!
pagingCounter: Int!
prevPage: Int
totalDocs: Int
totalPages: Int
totalDocs: Int!
totalPages: Int!
}
input Post_where {
@@ -321,6 +321,7 @@ type User {
hash: String
loginAttempts: Float
lockUntil: DateTime
sessions: [User_Sessions!]
}
"""
@@ -328,24 +329,33 @@ A field whose value conforms to the standard internet email address format as sp
"""
scalar EmailAddress @specifiedBy(url: "https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address")
type User_Sessions {
id: String
createdAt: DateTime
expiresAt: DateTime
}
type Users {
docs: [User]
hasNextPage: Boolean
hasPrevPage: Boolean
limit: Int
docs: [User!]!
hasNextPage: Boolean!
hasPrevPage: Boolean!
limit: Int!
nextPage: Int
offset: Int
page: Int
pagingCounter: Int
page: Int!
pagingCounter: Int!
prevPage: Int
totalDocs: Int
totalPages: Int
totalDocs: Int!
totalPages: Int!
}
input User_where {
updatedAt: User_updatedAt_operator
createdAt: User_createdAt_operator
email: User_email_operator
sessions__id: User_sessions__id_operator
sessions__createdAt: User_sessions__createdAt_operator
sessions__expiresAt: User_sessions__expiresAt_operator
id: User_id_operator
AND: [User_where_and]
OR: [User_where_or]
@@ -383,6 +393,37 @@ input User_email_operator {
all: [EmailAddress]
}
input User_sessions__id_operator {
equals: String
not_equals: String
like: String
contains: String
in: [String]
not_in: [String]
all: [String]
}
input User_sessions__createdAt_operator {
equals: DateTime
not_equals: DateTime
greater_than_equal: DateTime
greater_than: DateTime
less_than_equal: DateTime
less_than: DateTime
like: DateTime
exists: Boolean
}
input User_sessions__expiresAt_operator {
equals: DateTime
not_equals: DateTime
greater_than_equal: DateTime
greater_than: DateTime
less_than_equal: DateTime
less_than: DateTime
like: DateTime
}
input User_id_operator {
equals: String
not_equals: String
@@ -398,6 +439,9 @@ input User_where_and {
updatedAt: User_updatedAt_operator
createdAt: User_createdAt_operator
email: User_email_operator
sessions__id: User_sessions__id_operator
sessions__createdAt: User_sessions__createdAt_operator
sessions__expiresAt: User_sessions__expiresAt_operator
id: User_id_operator
AND: [User_where_and]
OR: [User_where_or]
@@ -407,6 +451,9 @@ input User_where_or {
updatedAt: User_updatedAt_operator
createdAt: User_createdAt_operator
email: User_email_operator
sessions__id: User_sessions__id_operator
sessions__createdAt: User_sessions__createdAt_operator
sessions__expiresAt: User_sessions__expiresAt_operator
id: User_id_operator
AND: [User_where_and]
OR: [User_where_or]
@@ -429,6 +476,7 @@ type UsersDocAccessFields {
updatedAt: UsersDocAccessFields_updatedAt
createdAt: UsersDocAccessFields_createdAt
email: UsersDocAccessFields_email
sessions: UsersDocAccessFields_sessions
}
type UsersDocAccessFields_updatedAt {
@@ -500,6 +548,105 @@ type UsersDocAccessFields_email_Delete {
permission: Boolean!
}
type UsersDocAccessFields_sessions {
create: UsersDocAccessFields_sessions_Create
read: UsersDocAccessFields_sessions_Read
update: UsersDocAccessFields_sessions_Update
delete: UsersDocAccessFields_sessions_Delete
fields: UsersDocAccessFields_sessions_Fields
}
type UsersDocAccessFields_sessions_Create {
permission: Boolean!
}
type UsersDocAccessFields_sessions_Read {
permission: Boolean!
}
type UsersDocAccessFields_sessions_Update {
permission: Boolean!
}
type UsersDocAccessFields_sessions_Delete {
permission: Boolean!
}
type UsersDocAccessFields_sessions_Fields {
id: UsersDocAccessFields_sessions_id
createdAt: UsersDocAccessFields_sessions_createdAt
expiresAt: UsersDocAccessFields_sessions_expiresAt
}
type UsersDocAccessFields_sessions_id {
create: UsersDocAccessFields_sessions_id_Create
read: UsersDocAccessFields_sessions_id_Read
update: UsersDocAccessFields_sessions_id_Update
delete: UsersDocAccessFields_sessions_id_Delete
}
type UsersDocAccessFields_sessions_id_Create {
permission: Boolean!
}
type UsersDocAccessFields_sessions_id_Read {
permission: Boolean!
}
type UsersDocAccessFields_sessions_id_Update {
permission: Boolean!
}
type UsersDocAccessFields_sessions_id_Delete {
permission: Boolean!
}
type UsersDocAccessFields_sessions_createdAt {
create: UsersDocAccessFields_sessions_createdAt_Create
read: UsersDocAccessFields_sessions_createdAt_Read
update: UsersDocAccessFields_sessions_createdAt_Update
delete: UsersDocAccessFields_sessions_createdAt_Delete
}
type UsersDocAccessFields_sessions_createdAt_Create {
permission: Boolean!
}
type UsersDocAccessFields_sessions_createdAt_Read {
permission: Boolean!
}
type UsersDocAccessFields_sessions_createdAt_Update {
permission: Boolean!
}
type UsersDocAccessFields_sessions_createdAt_Delete {
permission: Boolean!
}
type UsersDocAccessFields_sessions_expiresAt {
create: UsersDocAccessFields_sessions_expiresAt_Create
read: UsersDocAccessFields_sessions_expiresAt_Read
update: UsersDocAccessFields_sessions_expiresAt_Update
delete: UsersDocAccessFields_sessions_expiresAt_Delete
}
type UsersDocAccessFields_sessions_expiresAt_Create {
permission: Boolean!
}
type UsersDocAccessFields_sessions_expiresAt_Read {
permission: Boolean!
}
type UsersDocAccessFields_sessions_expiresAt_Update {
permission: Boolean!
}
type UsersDocAccessFields_sessions_expiresAt_Delete {
permission: Boolean!
}
type UsersCreateDocAccess {
permission: Boolean!
where: JSONObject
@@ -566,17 +713,17 @@ enum PayloadLockedDocument_User_RelationTo {
union PayloadLockedDocument_User = User
type PayloadLockedDocuments {
docs: [PayloadLockedDocument]
hasNextPage: Boolean
hasPrevPage: Boolean
limit: Int
docs: [PayloadLockedDocument!]!
hasNextPage: Boolean!
hasPrevPage: Boolean!
limit: Int!
nextPage: Int
offset: Int
page: Int
pagingCounter: Int
page: Int!
pagingCounter: Int!
prevPage: Int
totalDocs: Int
totalPages: Int
totalDocs: Int!
totalPages: Int!
}
input PayloadLockedDocument_where {
@@ -851,17 +998,17 @@ enum PayloadPreference_User_RelationTo {
union PayloadPreference_User = User
type PayloadPreferences {
docs: [PayloadPreference]
hasNextPage: Boolean
hasPrevPage: Boolean
limit: Int
docs: [PayloadPreference!]!
hasNextPage: Boolean!
hasPrevPage: Boolean!
limit: Int!
nextPage: Int
offset: Int
page: Int
pagingCounter: Int
page: Int!
pagingCounter: Int!
prevPage: Int
totalDocs: Int
totalPages: Int
totalDocs: Int!
totalPages: Int!
}
input PayloadPreference_where {
@@ -1287,6 +1434,7 @@ type UsersFields {
updatedAt: UsersFields_updatedAt
createdAt: UsersFields_createdAt
email: UsersFields_email
sessions: UsersFields_sessions
}
type UsersFields_updatedAt {
@@ -1358,6 +1506,105 @@ type UsersFields_email_Delete {
permission: Boolean!
}
type UsersFields_sessions {
create: UsersFields_sessions_Create
read: UsersFields_sessions_Read
update: UsersFields_sessions_Update
delete: UsersFields_sessions_Delete
fields: UsersFields_sessions_Fields
}
type UsersFields_sessions_Create {
permission: Boolean!
}
type UsersFields_sessions_Read {
permission: Boolean!
}
type UsersFields_sessions_Update {
permission: Boolean!
}
type UsersFields_sessions_Delete {
permission: Boolean!
}
type UsersFields_sessions_Fields {
id: UsersFields_sessions_id
createdAt: UsersFields_sessions_createdAt
expiresAt: UsersFields_sessions_expiresAt
}
type UsersFields_sessions_id {
create: UsersFields_sessions_id_Create
read: UsersFields_sessions_id_Read
update: UsersFields_sessions_id_Update
delete: UsersFields_sessions_id_Delete
}
type UsersFields_sessions_id_Create {
permission: Boolean!
}
type UsersFields_sessions_id_Read {
permission: Boolean!
}
type UsersFields_sessions_id_Update {
permission: Boolean!
}
type UsersFields_sessions_id_Delete {
permission: Boolean!
}
type UsersFields_sessions_createdAt {
create: UsersFields_sessions_createdAt_Create
read: UsersFields_sessions_createdAt_Read
update: UsersFields_sessions_createdAt_Update
delete: UsersFields_sessions_createdAt_Delete
}
type UsersFields_sessions_createdAt_Create {
permission: Boolean!
}
type UsersFields_sessions_createdAt_Read {
permission: Boolean!
}
type UsersFields_sessions_createdAt_Update {
permission: Boolean!
}
type UsersFields_sessions_createdAt_Delete {
permission: Boolean!
}
type UsersFields_sessions_expiresAt {
create: UsersFields_sessions_expiresAt_Create
read: UsersFields_sessions_expiresAt_Read
update: UsersFields_sessions_expiresAt_Update
delete: UsersFields_sessions_expiresAt_Delete
}
type UsersFields_sessions_expiresAt_Create {
permission: Boolean!
}
type UsersFields_sessions_expiresAt_Read {
permission: Boolean!
}
type UsersFields_sessions_expiresAt_Update {
permission: Boolean!
}
type UsersFields_sessions_expiresAt_Delete {
permission: Boolean!
}
type UsersCreateAccess {
permission: Boolean!
where: JSONObject
@@ -1687,26 +1934,26 @@ type PayloadPreferencesDeleteAccess {
type Mutation {
createPost(data: mutationPostInput!, draft: Boolean): Post
updatePost(id: String!, autosave: Boolean, data: mutationPostUpdateInput!, draft: Boolean): Post
deletePost(id: String!): Post
updatePost(id: String!, autosave: Boolean, data: mutationPostUpdateInput!, draft: Boolean, trash: Boolean): Post
deletePost(id: String!, trash: Boolean): Post
duplicatePost(id: String!, data: mutationPostInput!): Post
createUser(data: mutationUserInput!, draft: Boolean): User
updateUser(id: String!, autosave: Boolean, data: mutationUserUpdateInput!, draft: Boolean): User
deleteUser(id: String!): User
updateUser(id: String!, autosave: Boolean, data: mutationUserUpdateInput!, draft: Boolean, trash: Boolean): User
deleteUser(id: String!, trash: Boolean): User
refreshTokenUser: usersRefreshedUser
logoutUser: String
logoutUser(allSessions: Boolean): String
unlockUser(email: String!): Boolean!
loginUser(email: String!, password: String): usersLoginResult
forgotPasswordUser(disableEmail: Boolean, expiration: Int, email: String!): Boolean!
resetPasswordUser(password: String, token: String): usersResetPassword
verifyEmailUser(token: String): Boolean
createPayloadLockedDocument(data: mutationPayloadLockedDocumentInput!, draft: Boolean): PayloadLockedDocument
updatePayloadLockedDocument(id: String!, autosave: Boolean, data: mutationPayloadLockedDocumentUpdateInput!, draft: Boolean): PayloadLockedDocument
deletePayloadLockedDocument(id: String!): PayloadLockedDocument
updatePayloadLockedDocument(id: String!, autosave: Boolean, data: mutationPayloadLockedDocumentUpdateInput!, draft: Boolean, trash: Boolean): PayloadLockedDocument
deletePayloadLockedDocument(id: String!, trash: Boolean): PayloadLockedDocument
duplicatePayloadLockedDocument(id: String!, data: mutationPayloadLockedDocumentInput!): PayloadLockedDocument
createPayloadPreference(data: mutationPayloadPreferenceInput!, draft: Boolean): PayloadPreference
updatePayloadPreference(id: String!, autosave: Boolean, data: mutationPayloadPreferenceUpdateInput!, draft: Boolean): PayloadPreference
deletePayloadPreference(id: String!): PayloadPreference
updatePayloadPreference(id: String!, autosave: Boolean, data: mutationPayloadPreferenceUpdateInput!, draft: Boolean, trash: Boolean): PayloadPreference
deletePayloadPreference(id: String!, trash: Boolean): PayloadPreference
duplicatePayloadPreference(id: String!, data: mutationPayloadPreferenceInput!): PayloadPreference
}
@@ -1736,9 +1983,16 @@ input mutationUserInput {
hash: String
loginAttempts: Float
lockUntil: String
sessions: [mutationUser_SessionsInput]
password: String!
}
input mutationUser_SessionsInput {
id: String!
createdAt: String
expiresAt: String!
}
input mutationUserUpdateInput {
updatedAt: String
createdAt: String
@@ -1749,9 +2003,16 @@ input mutationUserUpdateInput {
hash: String
loginAttempts: Float
lockUntil: String
sessions: [mutationUserUpdate_SessionsInput]
password: String
}
input mutationUserUpdate_SessionsInput {
id: String!
createdAt: String
expiresAt: String!
}
type usersRefreshedUser {
exp: Int
refreshedToken: String