fix: join field with the target relationship inside localized array (#10621)

Fixes https://github.com/payloadcms/payload/issues/10356
This commit is contained in:
Sasha
2025-01-21 02:18:26 +02:00
committed by GitHub
parent 46c1b375b8
commit 25a70ab455
8 changed files with 133 additions and 20 deletions

View File

@@ -151,11 +151,19 @@ export const buildJoinAggregation = async ({
join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : ''
const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${localeSuffix}`
let foreignField: string
if (join.getForeignPath) {
foreignField = `${join.getForeignPath({ locale })}${polymorphicSuffix}`
} else {
foreignField = `${join.field.on}${polymorphicSuffix}`
}
aggregate.push(
{
$lookup: {
as: `${as}.docs`,
foreignField: `${join.field.on}${localeSuffix}${polymorphicSuffix}`,
foreignField,
from: adapter.collections[slug].collection.name,
localField: versions ? 'parent' : '_id',
pipeline,

View File

@@ -517,6 +517,7 @@ export type SanitizedJoin = {
* The field configuration defining the join
*/
field: JoinField
getForeignPath?(args: { locale?: TypedLocale }): string
/**
* The path of the join field in dot notation
*/

View File

@@ -39,6 +39,7 @@ export const sanitizeJoinField = ({
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 }) => {
@@ -48,6 +49,27 @@ export const sanitizeJoinField = ({
const currentSegment = pathSegments[currentSegmentIndex]
// match field on path segments
if ('name' in field && field.name === currentSegment) {
if ('localized' in field && field.localized) {
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 &&
@@ -78,7 +100,8 @@ export const sanitizeJoinField = ({
join.targetField = joinRelationship
// override the join field localized property to use whatever the relationship field has
field.localized = joinRelationship.localized
// or if it's nested to a localized array / blocks / tabs / group
field.localized = localized
// override the join field hasMany property to use whatever the relationship field has
field.hasMany = joinRelationship.hasMany

View File

@@ -109,29 +109,20 @@ export const traverseFields = ({
if (field.type === 'tabs' && 'tabs' in field) {
for (const tab of field.tabs) {
let tabRef = ref
if (skip) {
return false
}
if ('name' in tab && tab.name) {
if (!ref[tab.name] || typeof ref[tab.name] !== 'object') {
if (fillEmpty) {
ref[tab.name] = {}
} else {
continue
}
}
tabRef = tabRef[tab.name]
if (tab.localized) {
for (const key in tabRef as Record<string, unknown>) {
if (tabRef[key] && typeof tabRef[key] === 'object') {
traverseFields({
callback,
fields: tab.fields,
fillEmpty,
parentRef: currentParentRef,
ref: tabRef[key],
})
}
ref[tab.name] = { en: {} }
} else {
ref[tab.name] = {}
}
} else {
continue
}
}
@@ -148,6 +139,36 @@ export const traverseFields = ({
return true
}
tabRef = tabRef[tab.name]
if (tab.localized) {
for (const key in tabRef as Record<string, unknown>) {
if (tabRef[key] && typeof tabRef[key] === 'object') {
traverseFields({
callback,
fields: tab.fields,
fillEmpty,
parentRef: currentParentRef,
ref: tabRef[key],
})
}
}
}
} else {
if (
callback &&
callback({
field: { ...tab, type: 'tab' },
next,
parentRef: currentParentRef,
ref: tabRef,
})
) {
return true
}
}
if (!tab.localized) {
traverseFields({
callback,
fields: tab.fields,
@@ -157,6 +178,11 @@ export const traverseFields = ({
})
}
if (skip) {
return false
}
}
return
}
@@ -166,10 +192,18 @@ export const traverseFields = ({
if (!ref[field.name]) {
if (fillEmpty) {
if (field.type === 'group') {
if (field.localized) {
ref[field.name] = {
en: {},
}
} else {
ref[field.name] = {}
}
} else if (field.type === 'array' || field.type === 'blocks') {
if (field.localized) {
ref[field.name] = {}
ref[field.name] = {
en: [],
}
} else {
ref[field.name] = []
}

View File

@@ -109,6 +109,12 @@ export const Categories: CollectionConfig = {
collection: 'posts',
on: 'array.category',
},
{
name: 'localizedArrayPosts',
type: 'join',
collection: 'posts',
on: 'localizedArray.category',
},
{
name: 'blocksPosts',
type: 'join',

View File

@@ -104,6 +104,18 @@ export const Posts: CollectionConfig = {
},
],
},
{
name: 'localizedArray',
type: 'array',
localized: true,
fields: [
{
name: 'category',
type: 'relationship',
relationTo: categoriesSlug,
},
],
},
{
name: 'blocks',
type: 'blocks',

View File

@@ -115,6 +115,7 @@ describe('Joins Field', () => {
camelCaseCategory: category.id,
},
array: [{ category: category.id }],
localizedArray: [{ category: category.id }],
blocks: [{ blockType: 'block', category: category.id }],
})
}
@@ -214,6 +215,16 @@ describe('Joins Field', () => {
expect(categoryWithPosts.arrayPosts.docs).toBeDefined()
})
it('should populate joins with localized array relationships', async () => {
const categoryWithPosts = await payload.findByID({
id: category.id,
collection: categoriesSlug,
})
expect(categoryWithPosts.localizedArrayPosts.docs).toBeDefined()
expect(categoryWithPosts.localizedArrayPosts.docs).toHaveLength(10)
})
it('should populate joins with blocks relationships', async () => {
const categoryWithPosts = await payload.findByID({
id: category.id,

View File

@@ -41,6 +41,7 @@ export interface Config {
'group.relatedPosts': 'posts';
'group.camelCasePosts': 'posts';
arrayPosts: 'posts';
localizedArrayPosts: 'posts';
blocksPosts: 'posts';
polymorphic: 'posts';
polymorphics: 'posts';
@@ -199,6 +200,12 @@ export interface Post {
id?: string | null;
}[]
| null;
localizedArray?:
| {
category?: (string | null) | Category;
id?: string | null;
}[]
| null;
blocks?:
| {
category?: (string | null) | Category;
@@ -272,6 +279,10 @@ export interface Category {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
localizedArrayPosts?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
blocksPosts?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
@@ -649,6 +660,12 @@ export interface PostsSelect<T extends boolean = true> {
category?: T;
id?: T;
};
localizedArray?:
| T
| {
category?: T;
id?: T;
};
blocks?:
| T
| {
@@ -680,6 +697,7 @@ export interface CategoriesSelect<T extends boolean = true> {
camelCasePosts?: T;
};
arrayPosts?: T;
localizedArrayPosts?: T;
blocksPosts?: T;
polymorphic?: T;
polymorphics?: T;