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 { 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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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';
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user