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", "outputCapture": "std",
"request": "launch", "request": "launch",
"type": "node-terminal" "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 // 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) const subFields = buildFields(label, field.fields)
return { return {
@@ -81,6 +81,18 @@ const buildFields = (label, fieldsToBuild) =>
if (field.type === 'tabs') { if (field.type === 'tabs') {
return field.tabs.reduce( return field.tabs.reduce(
(fieldsWithTabFields, tab) => { (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 { return {
...fieldsWithTabFields, ...fieldsWithTabFields,
...buildFields(label, tab.fields), ...buildFields(label, tab.fields),

View File

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

View File

@@ -1,21 +1,21 @@
type Query { type Query {
Post(id: String!, draft: Boolean): Post Post(id: String!, draft: Boolean, trash: Boolean): Post
Posts(draft: Boolean, where: Post_where, limit: Int, page: Int, pagination: Boolean, sort: String): Posts Posts(draft: Boolean, where: Post_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): Posts
countPosts(draft: Boolean, where: Post_where): countPosts countPosts(draft: Boolean, trash: Boolean, where: Post_where): countPosts
docAccessPost(id: String!): postsDocAccess docAccessPost(id: String!): postsDocAccess
User(id: String!, draft: Boolean): User User(id: String!, draft: Boolean, trash: Boolean): User
Users(draft: Boolean, where: User_where, limit: Int, page: Int, pagination: Boolean, sort: String): Users Users(draft: Boolean, where: User_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): Users
countUsers(draft: Boolean, where: User_where): countUsers countUsers(draft: Boolean, trash: Boolean, where: User_where): countUsers
docAccessUser(id: String!): usersDocAccess docAccessUser(id: String!): usersDocAccess
meUser: usersMe meUser: usersMe
initializedUser: Boolean initializedUser: Boolean
PayloadLockedDocument(id: String!, draft: Boolean): PayloadLockedDocument PayloadLockedDocument(id: String!, draft: Boolean, trash: Boolean): PayloadLockedDocument
PayloadLockedDocuments(draft: Boolean, where: PayloadLockedDocument_where, limit: Int, page: Int, pagination: Boolean, sort: String): PayloadLockedDocuments PayloadLockedDocuments(draft: Boolean, where: PayloadLockedDocument_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): PayloadLockedDocuments
countPayloadLockedDocuments(draft: Boolean, where: PayloadLockedDocument_where): countPayloadLockedDocuments countPayloadLockedDocuments(draft: Boolean, trash: Boolean, where: PayloadLockedDocument_where): countPayloadLockedDocuments
docAccessPayloadLockedDocument(id: String!): payload_locked_documentsDocAccess docAccessPayloadLockedDocument(id: String!): payload_locked_documentsDocAccess
PayloadPreference(id: String!, draft: Boolean): PayloadPreference PayloadPreference(id: String!, draft: Boolean, trash: Boolean): PayloadPreference
PayloadPreferences(draft: Boolean, where: PayloadPreference_where, limit: Int, page: Int, pagination: Boolean, sort: String): PayloadPreferences PayloadPreferences(draft: Boolean, where: PayloadPreference_where, limit: Int, page: Int, pagination: Boolean, sort: String, trash: Boolean): PayloadPreferences
countPayloadPreferences(draft: Boolean, where: PayloadPreference_where): countPayloadPreferences countPayloadPreferences(draft: Boolean, trash: Boolean, where: PayloadPreference_where): countPayloadPreferences
docAccessPayloadPreference(id: String!): payload_preferencesDocAccess docAccessPayloadPreference(id: String!): payload_preferencesDocAccess
Access: Access 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 scalar DateTime
type Posts { type Posts {
docs: [Post] docs: [Post!]!
hasNextPage: Boolean hasNextPage: Boolean!
hasPrevPage: Boolean hasPrevPage: Boolean!
limit: Int limit: Int!
nextPage: Int nextPage: Int
offset: Int offset: Int
page: Int page: Int!
pagingCounter: Int pagingCounter: Int!
prevPage: Int prevPage: Int
totalDocs: Int totalDocs: Int!
totalPages: Int totalPages: Int!
} }
input Post_where { input Post_where {
@@ -321,6 +321,7 @@ type User {
hash: String hash: String
loginAttempts: Float loginAttempts: Float
lockUntil: DateTime 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") 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 { type Users {
docs: [User] docs: [User!]!
hasNextPage: Boolean hasNextPage: Boolean!
hasPrevPage: Boolean hasPrevPage: Boolean!
limit: Int limit: Int!
nextPage: Int nextPage: Int
offset: Int offset: Int
page: Int page: Int!
pagingCounter: Int pagingCounter: Int!
prevPage: Int prevPage: Int
totalDocs: Int totalDocs: Int!
totalPages: Int totalPages: Int!
} }
input User_where { input User_where {
updatedAt: User_updatedAt_operator updatedAt: User_updatedAt_operator
createdAt: User_createdAt_operator createdAt: User_createdAt_operator
email: User_email_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 id: User_id_operator
AND: [User_where_and] AND: [User_where_and]
OR: [User_where_or] OR: [User_where_or]
@@ -383,6 +393,37 @@ input User_email_operator {
all: [EmailAddress] 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 { input User_id_operator {
equals: String equals: String
not_equals: String not_equals: String
@@ -398,6 +439,9 @@ input User_where_and {
updatedAt: User_updatedAt_operator updatedAt: User_updatedAt_operator
createdAt: User_createdAt_operator createdAt: User_createdAt_operator
email: User_email_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 id: User_id_operator
AND: [User_where_and] AND: [User_where_and]
OR: [User_where_or] OR: [User_where_or]
@@ -407,6 +451,9 @@ input User_where_or {
updatedAt: User_updatedAt_operator updatedAt: User_updatedAt_operator
createdAt: User_createdAt_operator createdAt: User_createdAt_operator
email: User_email_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 id: User_id_operator
AND: [User_where_and] AND: [User_where_and]
OR: [User_where_or] OR: [User_where_or]
@@ -429,6 +476,7 @@ type UsersDocAccessFields {
updatedAt: UsersDocAccessFields_updatedAt updatedAt: UsersDocAccessFields_updatedAt
createdAt: UsersDocAccessFields_createdAt createdAt: UsersDocAccessFields_createdAt
email: UsersDocAccessFields_email email: UsersDocAccessFields_email
sessions: UsersDocAccessFields_sessions
} }
type UsersDocAccessFields_updatedAt { type UsersDocAccessFields_updatedAt {
@@ -500,6 +548,105 @@ type UsersDocAccessFields_email_Delete {
permission: Boolean! 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 { type UsersCreateDocAccess {
permission: Boolean! permission: Boolean!
where: JSONObject where: JSONObject
@@ -566,17 +713,17 @@ enum PayloadLockedDocument_User_RelationTo {
union PayloadLockedDocument_User = User union PayloadLockedDocument_User = User
type PayloadLockedDocuments { type PayloadLockedDocuments {
docs: [PayloadLockedDocument] docs: [PayloadLockedDocument!]!
hasNextPage: Boolean hasNextPage: Boolean!
hasPrevPage: Boolean hasPrevPage: Boolean!
limit: Int limit: Int!
nextPage: Int nextPage: Int
offset: Int offset: Int
page: Int page: Int!
pagingCounter: Int pagingCounter: Int!
prevPage: Int prevPage: Int
totalDocs: Int totalDocs: Int!
totalPages: Int totalPages: Int!
} }
input PayloadLockedDocument_where { input PayloadLockedDocument_where {
@@ -851,17 +998,17 @@ enum PayloadPreference_User_RelationTo {
union PayloadPreference_User = User union PayloadPreference_User = User
type PayloadPreferences { type PayloadPreferences {
docs: [PayloadPreference] docs: [PayloadPreference!]!
hasNextPage: Boolean hasNextPage: Boolean!
hasPrevPage: Boolean hasPrevPage: Boolean!
limit: Int limit: Int!
nextPage: Int nextPage: Int
offset: Int offset: Int
page: Int page: Int!
pagingCounter: Int pagingCounter: Int!
prevPage: Int prevPage: Int
totalDocs: Int totalDocs: Int!
totalPages: Int totalPages: Int!
} }
input PayloadPreference_where { input PayloadPreference_where {
@@ -1287,6 +1434,7 @@ type UsersFields {
updatedAt: UsersFields_updatedAt updatedAt: UsersFields_updatedAt
createdAt: UsersFields_createdAt createdAt: UsersFields_createdAt
email: UsersFields_email email: UsersFields_email
sessions: UsersFields_sessions
} }
type UsersFields_updatedAt { type UsersFields_updatedAt {
@@ -1358,6 +1506,105 @@ type UsersFields_email_Delete {
permission: Boolean! 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 { type UsersCreateAccess {
permission: Boolean! permission: Boolean!
where: JSONObject where: JSONObject
@@ -1687,26 +1934,26 @@ type PayloadPreferencesDeleteAccess {
type Mutation { type Mutation {
createPost(data: mutationPostInput!, draft: Boolean): Post createPost(data: mutationPostInput!, draft: Boolean): Post
updatePost(id: String!, autosave: Boolean, data: mutationPostUpdateInput!, draft: Boolean): Post updatePost(id: String!, autosave: Boolean, data: mutationPostUpdateInput!, draft: Boolean, trash: Boolean): Post
deletePost(id: String!): Post deletePost(id: String!, trash: Boolean): Post
duplicatePost(id: String!, data: mutationPostInput!): Post duplicatePost(id: String!, data: mutationPostInput!): Post
createUser(data: mutationUserInput!, draft: Boolean): User createUser(data: mutationUserInput!, draft: Boolean): User
updateUser(id: String!, autosave: Boolean, data: mutationUserUpdateInput!, draft: Boolean): User updateUser(id: String!, autosave: Boolean, data: mutationUserUpdateInput!, draft: Boolean, trash: Boolean): User
deleteUser(id: String!): User deleteUser(id: String!, trash: Boolean): User
refreshTokenUser: usersRefreshedUser refreshTokenUser: usersRefreshedUser
logoutUser: String logoutUser(allSessions: Boolean): String
unlockUser(email: String!): Boolean! unlockUser(email: String!): Boolean!
loginUser(email: String!, password: String): usersLoginResult loginUser(email: String!, password: String): usersLoginResult
forgotPasswordUser(disableEmail: Boolean, expiration: Int, email: String!): Boolean! forgotPasswordUser(disableEmail: Boolean, expiration: Int, email: String!): Boolean!
resetPasswordUser(password: String, token: String): usersResetPassword resetPasswordUser(password: String, token: String): usersResetPassword
verifyEmailUser(token: String): Boolean verifyEmailUser(token: String): Boolean
createPayloadLockedDocument(data: mutationPayloadLockedDocumentInput!, draft: Boolean): PayloadLockedDocument createPayloadLockedDocument(data: mutationPayloadLockedDocumentInput!, draft: Boolean): PayloadLockedDocument
updatePayloadLockedDocument(id: String!, autosave: Boolean, data: mutationPayloadLockedDocumentUpdateInput!, draft: Boolean): PayloadLockedDocument updatePayloadLockedDocument(id: String!, autosave: Boolean, data: mutationPayloadLockedDocumentUpdateInput!, draft: Boolean, trash: Boolean): PayloadLockedDocument
deletePayloadLockedDocument(id: String!): PayloadLockedDocument deletePayloadLockedDocument(id: String!, trash: Boolean): PayloadLockedDocument
duplicatePayloadLockedDocument(id: String!, data: mutationPayloadLockedDocumentInput!): PayloadLockedDocument duplicatePayloadLockedDocument(id: String!, data: mutationPayloadLockedDocumentInput!): PayloadLockedDocument
createPayloadPreference(data: mutationPayloadPreferenceInput!, draft: Boolean): PayloadPreference createPayloadPreference(data: mutationPayloadPreferenceInput!, draft: Boolean): PayloadPreference
updatePayloadPreference(id: String!, autosave: Boolean, data: mutationPayloadPreferenceUpdateInput!, draft: Boolean): PayloadPreference updatePayloadPreference(id: String!, autosave: Boolean, data: mutationPayloadPreferenceUpdateInput!, draft: Boolean, trash: Boolean): PayloadPreference
deletePayloadPreference(id: String!): PayloadPreference deletePayloadPreference(id: String!, trash: Boolean): PayloadPreference
duplicatePayloadPreference(id: String!, data: mutationPayloadPreferenceInput!): PayloadPreference duplicatePayloadPreference(id: String!, data: mutationPayloadPreferenceInput!): PayloadPreference
} }
@@ -1736,9 +1983,16 @@ input mutationUserInput {
hash: String hash: String
loginAttempts: Float loginAttempts: Float
lockUntil: String lockUntil: String
sessions: [mutationUser_SessionsInput]
password: String! password: String!
} }
input mutationUser_SessionsInput {
id: String!
createdAt: String
expiresAt: String!
}
input mutationUserUpdateInput { input mutationUserUpdateInput {
updatedAt: String updatedAt: String
createdAt: String createdAt: String
@@ -1749,9 +2003,16 @@ input mutationUserUpdateInput {
hash: String hash: String
loginAttempts: Float loginAttempts: Float
lockUntil: String lockUntil: String
sessions: [mutationUserUpdate_SessionsInput]
password: String password: String
} }
input mutationUserUpdate_SessionsInput {
id: String!
createdAt: String
expiresAt: String!
}
type usersRefreshedUser { type usersRefreshedUser {
exp: Int exp: Int
refreshedToken: String refreshedToken: String