fix: select with unnamed tabs (#8966)
### What? Fixes `select` handling for properties inside of unnamed tabs using the mongodb adapter. Additionally, refactors `traverseFields` in drizzle to reuse logic from groups / collapsible or rows if unnamed. ### Why? `select` must work for any fields. ### How? Fixes the `'tab'` case in `buildProjectionFromSelect` to handle when the field is an unnamed tab. Adds extra tests for named tabs / unnamed.
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
type SelectType,
|
||||
type TabAsField,
|
||||
} from 'payload'
|
||||
import { fieldAffectsData, getSelectMode } from 'payload/shared'
|
||||
import { fieldAffectsData, getSelectMode, tabHasName } from 'payload/shared'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
@@ -131,9 +131,17 @@ const traverseFields = ({
|
||||
|
||||
case 'group':
|
||||
case 'tab':
|
||||
case 'array':
|
||||
case 'array': {
|
||||
let fieldSelect: SelectType
|
||||
|
||||
if (field.type === 'tab' && !tabHasName(field)) {
|
||||
fieldSelect = select
|
||||
} else {
|
||||
fieldSelect = select[field.name] as SelectType
|
||||
}
|
||||
|
||||
if (field.type === 'array' && selectMode === 'include') {
|
||||
select[field.name]['id'] = true
|
||||
fieldSelect['id'] = true
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
@@ -141,12 +149,13 @@ const traverseFields = ({
|
||||
databaseSchemaPath: fieldDatabaseSchemaPath,
|
||||
fields: field.fields,
|
||||
projection,
|
||||
select: select[field.name] as SelectType,
|
||||
select: fieldSelect,
|
||||
selectMode,
|
||||
withinLocalizedField: fieldWithinLocalizedField,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const blocksSelect = select[field.name] as SelectType
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||
import type { Field, JoinQuery, SelectMode, SelectType } from 'payload'
|
||||
import type { Field, JoinQuery, SelectMode, SelectType, TabAsField } from 'payload'
|
||||
|
||||
import { and, eq, sql } from 'drizzle-orm'
|
||||
import { fieldAffectsData, fieldIsVirtual, tabHasName } from 'payload/shared'
|
||||
@@ -17,7 +17,7 @@ type TraverseFieldArgs = {
|
||||
currentArgs: Result
|
||||
currentTableName: string
|
||||
depth?: number
|
||||
fields: Field[]
|
||||
fields: (Field | TabAsField)[]
|
||||
joinQuery: JoinQuery
|
||||
joins?: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
@@ -77,7 +77,11 @@ export const traverseFields = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (field.type === 'collapsible' || field.type === 'row') {
|
||||
if (
|
||||
field.type === 'collapsible' ||
|
||||
field.type === 'row' ||
|
||||
(field.type === 'tab' && !tabHasName(field))
|
||||
) {
|
||||
traverseFields({
|
||||
_locales,
|
||||
adapter,
|
||||
@@ -100,43 +104,24 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
field.tabs.forEach((tab) => {
|
||||
const tabPath = tabHasName(tab) ? `${path}${tab.name}_` : path
|
||||
const tabTablePath = tabHasName(tab) ? `${tablePath}${toSnakeCase(tab.name)}_` : tablePath
|
||||
|
||||
const tabSelect = tabHasName(tab) ? select?.[tab.name] : select
|
||||
|
||||
if (tabSelect === false) {
|
||||
return
|
||||
}
|
||||
|
||||
let tabSelectAllOnCurrentLevel = selectAllOnCurrentLevel
|
||||
|
||||
if (tabHasName(tab) && select && !tabSelectAllOnCurrentLevel) {
|
||||
tabSelectAllOnCurrentLevel =
|
||||
select[tab.name] === true ||
|
||||
(selectMode === 'exclude' && typeof select[tab.name] === 'undefined')
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
_locales,
|
||||
adapter,
|
||||
currentArgs,
|
||||
currentTableName,
|
||||
depth,
|
||||
fields: tab.fields,
|
||||
joinQuery,
|
||||
joins,
|
||||
path: tabPath,
|
||||
select: typeof tabSelect === 'object' ? tabSelect : undefined,
|
||||
selectAllOnCurrentLevel: tabSelectAllOnCurrentLevel,
|
||||
selectMode,
|
||||
tablePath: tabTablePath,
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
versions,
|
||||
withTabledFields,
|
||||
})
|
||||
traverseFields({
|
||||
_locales,
|
||||
adapter,
|
||||
currentArgs,
|
||||
currentTableName,
|
||||
depth,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
joinQuery,
|
||||
joins,
|
||||
path,
|
||||
select,
|
||||
selectAllOnCurrentLevel,
|
||||
selectMode,
|
||||
tablePath,
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
versions,
|
||||
withTabledFields,
|
||||
})
|
||||
|
||||
return
|
||||
@@ -369,10 +354,11 @@ export const traverseFields = ({
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
const groupSelect = select?.[field.name]
|
||||
case 'group':
|
||||
case 'tab': {
|
||||
const fieldSelect = select?.[field.name]
|
||||
|
||||
if (groupSelect === false) {
|
||||
if (fieldSelect === false) {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -386,11 +372,11 @@ export const traverseFields = ({
|
||||
joinQuery,
|
||||
joins,
|
||||
path: `${path}${field.name}_`,
|
||||
select: typeof groupSelect === 'object' ? groupSelect : undefined,
|
||||
select: typeof fieldSelect === 'object' ? fieldSelect : undefined,
|
||||
selectAllOnCurrentLevel:
|
||||
selectAllOnCurrentLevel ||
|
||||
groupSelect === true ||
|
||||
(selectMode === 'exclude' && typeof groupSelect === 'undefined'),
|
||||
fieldSelect === true ||
|
||||
(selectMode === 'exclude' && typeof fieldSelect === 'undefined'),
|
||||
selectMode,
|
||||
tablePath: `${tablePath}${toSnakeCase(field.name)}_`,
|
||||
topLevelArgs,
|
||||
|
||||
@@ -74,5 +74,36 @@ export const PostsCollection: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tab Unnamed',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'unnamedTabText',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'unnamedTabNumber',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -129,6 +129,55 @@ describe('Select', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should select all the fields inside of named tab', async () => {
|
||||
const res = await payload.findByID({
|
||||
collection: 'posts',
|
||||
id: postId,
|
||||
select: {
|
||||
tab: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(res).toStrictEqual({
|
||||
id: postId,
|
||||
tab: post.tab,
|
||||
})
|
||||
})
|
||||
|
||||
it('should select text field inside of named tab', async () => {
|
||||
const res = await payload.findByID({
|
||||
collection: 'posts',
|
||||
id: postId,
|
||||
select: {
|
||||
tab: {
|
||||
text: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(res).toStrictEqual({
|
||||
id: postId,
|
||||
tab: {
|
||||
text: post.tab.text,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should select text field inside of unnamed tab', async () => {
|
||||
const res = await payload.findByID({
|
||||
collection: 'posts',
|
||||
id: postId,
|
||||
select: {
|
||||
unnamedTabText: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(res).toStrictEqual({
|
||||
id: postId,
|
||||
unnamedTabText: post.unnamedTabText,
|
||||
})
|
||||
})
|
||||
|
||||
it('should select id as default from array', async () => {
|
||||
const res = await payload.findByID({
|
||||
collection: 'posts',
|
||||
@@ -1662,7 +1711,7 @@ describe('Select', () => {
|
||||
expect(richTextSlateRel.value).toStrictEqual(expectedHomePage)
|
||||
})
|
||||
|
||||
it('REST API - should populate with the defaultPopulate select shape', async () => {
|
||||
it('rEST API - should populate with the defaultPopulate select shape', async () => {
|
||||
const restResult = await (
|
||||
await restClient.GET(`/pages/${aboutPage.id}`, { query: { depth: 1 } })
|
||||
).json()
|
||||
@@ -1728,6 +1777,12 @@ function createPost() {
|
||||
number: 1,
|
||||
},
|
||||
],
|
||||
tab: {
|
||||
text: 'text',
|
||||
number: 1,
|
||||
},
|
||||
unnamedTabNumber: 2,
|
||||
unnamedTabText: 'text2',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ export interface Config {
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs?: {
|
||||
tasks: unknown;
|
||||
workflows?: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
forgotPassword: {
|
||||
@@ -101,6 +105,12 @@ export interface Post {
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
tab?: {
|
||||
text?: string | null;
|
||||
number?: number | null;
|
||||
};
|
||||
unnamedTabText?: string | null;
|
||||
unnamedTabNumber?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -424,6 +434,14 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
tab?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
number?: T;
|
||||
};
|
||||
unnamedTabText?: T;
|
||||
unnamedTabNumber?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user