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:
@@ -4,6 +4,8 @@ import type { Config, SanitizedConfig } from '../../config/types.js'
|
||||
|
||||
import { APIError } from '../../errors/index.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 {
|
||||
fieldShouldBeLocalized,
|
||||
@@ -74,86 +76,40 @@ export const sanitizeJoinField = ({
|
||||
if (!joinCollection) {
|
||||
throw new InvalidFieldJoin(field)
|
||||
}
|
||||
let joinRelationship: RelationshipField | UploadField
|
||||
|
||||
const pathSegments = field.on.split('.') // Split the schema path into segments
|
||||
let currentSegmentIndex = 0
|
||||
|
||||
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,
|
||||
const relationshipField = getFieldByPath({
|
||||
fields: flattenAllFields({ cache: true, fields: joinCollection.fields }),
|
||||
path: field.on,
|
||||
})
|
||||
|
||||
if (!joinRelationship) {
|
||||
if (
|
||||
!relationshipField ||
|
||||
(relationshipField.field.type !== 'relationship' && relationshipField.field.type !== 'upload')
|
||||
) {
|
||||
throw new InvalidFieldJoin(join.field)
|
||||
}
|
||||
|
||||
if (!joinRelationship.index && !joinRelationship.unique) {
|
||||
joinRelationship.index = true
|
||||
if (relationshipField.pathHasLocalized) {
|
||||
join.getForeignPath = ({ locale }) => {
|
||||
return relationshipField.localizedPath.replace('<locale>', locale)
|
||||
}
|
||||
}
|
||||
|
||||
if (!relationshipField.field.index && !relationshipField.field.unique) {
|
||||
relationshipField.field.index = true
|
||||
}
|
||||
|
||||
if (validateOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
join.targetField = joinRelationship
|
||||
join.targetField = relationshipField.field
|
||||
|
||||
// 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
|
||||
field.localized = localized
|
||||
field.localized = relationshipField.field.localized
|
||||
// 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
|
||||
field.targetField = join.targetField
|
||||
|
||||
@@ -162,6 +162,12 @@ export const Categories: CollectionConfig = {
|
||||
isFiltered: { not_equals: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'inTab',
|
||||
type: 'join',
|
||||
collection: postsSlug,
|
||||
on: 'tab.category',
|
||||
},
|
||||
{
|
||||
name: 'joinWithError',
|
||||
type: 'join',
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ export interface Config {
|
||||
localizedPolymorphic: 'posts';
|
||||
localizedPolymorphics: 'posts';
|
||||
filtered: 'posts';
|
||||
inTab: 'posts';
|
||||
joinWithError: 'posts';
|
||||
hiddenPosts: 'hidden-posts';
|
||||
singulars: 'singular';
|
||||
@@ -316,6 +317,12 @@ export interface Post {
|
||||
blockType: 'block';
|
||||
}[]
|
||||
| null;
|
||||
first?: {
|
||||
tabText?: string | null;
|
||||
};
|
||||
tab?: {
|
||||
category?: (string | null) | Category;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -429,6 +436,11 @@ export interface Category {
|
||||
hasNextPage?: boolean;
|
||||
totalDocs?: number;
|
||||
};
|
||||
inTab?: {
|
||||
docs?: (string | Post)[];
|
||||
hasNextPage?: boolean;
|
||||
totalDocs?: number;
|
||||
};
|
||||
joinWithError?: {
|
||||
docs?: (string | Post)[];
|
||||
hasNextPage?: boolean;
|
||||
@@ -935,6 +947,16 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
first?:
|
||||
| T
|
||||
| {
|
||||
tabText?: T;
|
||||
};
|
||||
tab?:
|
||||
| T
|
||||
| {
|
||||
category?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
@@ -963,6 +985,7 @@ export interface CategoriesSelect<T extends boolean = true> {
|
||||
localizedPolymorphics?: T;
|
||||
singulars?: T;
|
||||
filtered?: T;
|
||||
inTab?: T;
|
||||
joinWithError?: T;
|
||||
enableErrorOnJoin?: T;
|
||||
updatedAt?: T;
|
||||
|
||||
Reference in New Issue
Block a user