fix: cannot define a join field when the target relationship is nested to a second or higher tab (#12041)

Fixes https://github.com/payloadcms/payload/issues/11720
This commit is contained in:
Sasha
2025-04-10 15:36:03 +03:00
committed by GitHub
parent a0fb3353c6
commit 7aa3c5ea6b
4 changed files with 78 additions and 64 deletions

View File

@@ -4,6 +4,8 @@ import type { Config, SanitizedConfig } from '../../config/types.js'
import { APIError } from '../../errors/index.js' import { APIError } from '../../errors/index.js'
import { InvalidFieldJoin } from '../../errors/InvalidFieldJoin.js' import { InvalidFieldJoin } from '../../errors/InvalidFieldJoin.js'
import { flattenAllFields } from '../../utilities/flattenAllFields.js'
import { getFieldByPath } from '../../utilities/getFieldByPath.js'
import { traverseFields } from '../../utilities/traverseFields.js' import { traverseFields } from '../../utilities/traverseFields.js'
import { import {
fieldShouldBeLocalized, fieldShouldBeLocalized,
@@ -74,86 +76,40 @@ export const sanitizeJoinField = ({
if (!joinCollection) { if (!joinCollection) {
throw new InvalidFieldJoin(field) throw new InvalidFieldJoin(field)
} }
let joinRelationship: RelationshipField | UploadField
const pathSegments = field.on.split('.') // Split the schema path into segments const relationshipField = getFieldByPath({
let currentSegmentIndex = 0 fields: flattenAllFields({ cache: true, fields: joinCollection.fields }),
path: field.on,
let localized = false
// Traverse fields and match based on the schema path
traverseFields({
callback: ({ field, next, parentIsLocalized }) => {
if (!('name' in field) || !field.name) {
return
}
const currentSegment = pathSegments[currentSegmentIndex]
// match field on path segments
if ('name' in field && field.name === currentSegment) {
if (fieldShouldBeLocalized({ field, parentIsLocalized })) {
localized = true
const fieldIndex = currentSegmentIndex
join.getForeignPath = ({ locale }) => {
return pathSegments.reduce((acc, segment, index) => {
let result = `${acc}${segment}`
if (index === fieldIndex) {
result = `${result}.${locale}`
}
if (index !== pathSegments.length - 1) {
result = `${result}.`
}
return result
}, '')
}
}
// Check if this is the last segment in the path
if (
(currentSegmentIndex === pathSegments.length - 1 &&
'type' in field &&
field.type === 'relationship') ||
field.type === 'upload'
) {
joinRelationship = field // Return the matched field
next()
return true
} else {
// Move to the next path segment and continue traversal
currentSegmentIndex++
}
} else {
// skip fields in non-matching path segments
next()
return
}
},
config: config as unknown as SanitizedConfig,
fields: joinCollection.fields,
parentIsLocalized: false,
}) })
if (!joinRelationship) { if (
!relationshipField ||
(relationshipField.field.type !== 'relationship' && relationshipField.field.type !== 'upload')
) {
throw new InvalidFieldJoin(join.field) throw new InvalidFieldJoin(join.field)
} }
if (!joinRelationship.index && !joinRelationship.unique) { if (relationshipField.pathHasLocalized) {
joinRelationship.index = true join.getForeignPath = ({ locale }) => {
return relationshipField.localizedPath.replace('<locale>', locale)
}
}
if (!relationshipField.field.index && !relationshipField.field.unique) {
relationshipField.field.index = true
} }
if (validateOnly) { if (validateOnly) {
return return
} }
join.targetField = joinRelationship join.targetField = relationshipField.field
// override the join field localized property to use whatever the relationship field has // override the join field localized property to use whatever the relationship field has
// or if it's nested to a localized array / blocks / tabs / group // or if it's nested to a localized array / blocks / tabs / group
field.localized = localized field.localized = relationshipField.field.localized
// override the join field hasMany property to use whatever the relationship field has // override the join field hasMany property to use whatever the relationship field has
field.hasMany = joinRelationship.hasMany field.hasMany = relationshipField.field.hasMany
// @ts-expect-error converting JoinField to FlattenedJoinField to track targetField // @ts-expect-error converting JoinField to FlattenedJoinField to track targetField
field.targetField = join.targetField field.targetField = join.targetField

View File

@@ -162,6 +162,12 @@ export const Categories: CollectionConfig = {
isFiltered: { not_equals: true }, isFiltered: { not_equals: true },
}, },
}, },
{
name: 'inTab',
type: 'join',
collection: postsSlug,
on: 'tab.category',
},
{ {
name: 'joinWithError', name: 'joinWithError',
type: 'join', type: 'join',

View File

@@ -142,5 +142,34 @@ export const Posts: CollectionConfig = {
}, },
], ],
}, },
{
type: 'tabs',
tabs: [
{
name: 'first',
fields: [
{
type: 'text',
name: 'tabText',
},
],
},
{
name: 'tab',
fields: [
{
type: 'row',
fields: [
{
name: 'category',
type: 'relationship',
relationTo: categoriesSlug,
},
],
},
],
},
],
},
], ],
} }

View File

@@ -113,6 +113,7 @@ export interface Config {
localizedPolymorphic: 'posts'; localizedPolymorphic: 'posts';
localizedPolymorphics: 'posts'; localizedPolymorphics: 'posts';
filtered: 'posts'; filtered: 'posts';
inTab: 'posts';
joinWithError: 'posts'; joinWithError: 'posts';
hiddenPosts: 'hidden-posts'; hiddenPosts: 'hidden-posts';
singulars: 'singular'; singulars: 'singular';
@@ -316,6 +317,12 @@ export interface Post {
blockType: 'block'; blockType: 'block';
}[] }[]
| null; | null;
first?: {
tabText?: string | null;
};
tab?: {
category?: (string | null) | Category;
};
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -429,6 +436,11 @@ export interface Category {
hasNextPage?: boolean; hasNextPage?: boolean;
totalDocs?: number; totalDocs?: number;
}; };
inTab?: {
docs?: (string | Post)[];
hasNextPage?: boolean;
totalDocs?: number;
};
joinWithError?: { joinWithError?: {
docs?: (string | Post)[]; docs?: (string | Post)[];
hasNextPage?: boolean; hasNextPage?: boolean;
@@ -935,6 +947,16 @@ export interface PostsSelect<T extends boolean = true> {
blockName?: T; blockName?: T;
}; };
}; };
first?:
| T
| {
tabText?: T;
};
tab?:
| T
| {
category?: T;
};
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
@@ -963,6 +985,7 @@ export interface CategoriesSelect<T extends boolean = true> {
localizedPolymorphics?: T; localizedPolymorphics?: T;
singulars?: T; singulars?: T;
filtered?: T; filtered?: T;
inTab?: T;
joinWithError?: T; joinWithError?: T;
enableErrorOnJoin?: T; enableErrorOnJoin?: T;
updatedAt?: T; updatedAt?: T;