fix: join field with the target relationship inside localized array (#10621)
Fixes https://github.com/payloadcms/payload/issues/10356
This commit is contained in:
@@ -151,11 +151,19 @@ export const buildJoinAggregation = async ({
|
|||||||
join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : ''
|
join.field.localized && adapter.payload.config.localization && locale ? `.${locale}` : ''
|
||||||
const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${localeSuffix}`
|
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(
|
aggregate.push(
|
||||||
{
|
{
|
||||||
$lookup: {
|
$lookup: {
|
||||||
as: `${as}.docs`,
|
as: `${as}.docs`,
|
||||||
foreignField: `${join.field.on}${localeSuffix}${polymorphicSuffix}`,
|
foreignField,
|
||||||
from: adapter.collections[slug].collection.name,
|
from: adapter.collections[slug].collection.name,
|
||||||
localField: versions ? 'parent' : '_id',
|
localField: versions ? 'parent' : '_id',
|
||||||
pipeline,
|
pipeline,
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ export type SanitizedJoin = {
|
|||||||
* The field configuration defining the join
|
* The field configuration defining the join
|
||||||
*/
|
*/
|
||||||
field: JoinField
|
field: JoinField
|
||||||
|
getForeignPath?(args: { locale?: TypedLocale }): string
|
||||||
/**
|
/**
|
||||||
* The path of the join field in dot notation
|
* The path of the join field in dot notation
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const sanitizeJoinField = ({
|
|||||||
const pathSegments = field.on.split('.') // Split the schema path into segments
|
const pathSegments = field.on.split('.') // Split the schema path into segments
|
||||||
let currentSegmentIndex = 0
|
let currentSegmentIndex = 0
|
||||||
|
|
||||||
|
let localized = false
|
||||||
// Traverse fields and match based on the schema path
|
// Traverse fields and match based on the schema path
|
||||||
traverseFields({
|
traverseFields({
|
||||||
callback: ({ field, next }) => {
|
callback: ({ field, next }) => {
|
||||||
@@ -48,6 +49,27 @@ export const sanitizeJoinField = ({
|
|||||||
const currentSegment = pathSegments[currentSegmentIndex]
|
const currentSegment = pathSegments[currentSegmentIndex]
|
||||||
// match field on path segments
|
// match field on path segments
|
||||||
if ('name' in field && field.name === currentSegment) {
|
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
|
// Check if this is the last segment in the path
|
||||||
if (
|
if (
|
||||||
(currentSegmentIndex === pathSegments.length - 1 &&
|
(currentSegmentIndex === pathSegments.length - 1 &&
|
||||||
@@ -78,7 +100,8 @@ export const sanitizeJoinField = ({
|
|||||||
join.targetField = joinRelationship
|
join.targetField = joinRelationship
|
||||||
|
|
||||||
// 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
|
||||||
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
|
// override the join field hasMany property to use whatever the relationship field has
|
||||||
field.hasMany = joinRelationship.hasMany
|
field.hasMany = joinRelationship.hasMany
|
||||||
|
|
||||||
|
|||||||
@@ -109,15 +109,36 @@ export const traverseFields = ({
|
|||||||
if (field.type === 'tabs' && 'tabs' in field) {
|
if (field.type === 'tabs' && 'tabs' in field) {
|
||||||
for (const tab of field.tabs) {
|
for (const tab of field.tabs) {
|
||||||
let tabRef = ref
|
let tabRef = ref
|
||||||
|
|
||||||
|
if (skip) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if ('name' in tab && tab.name) {
|
if ('name' in tab && tab.name) {
|
||||||
if (!ref[tab.name] || typeof ref[tab.name] !== 'object') {
|
if (!ref[tab.name] || typeof ref[tab.name] !== 'object') {
|
||||||
if (fillEmpty) {
|
if (fillEmpty) {
|
||||||
ref[tab.name] = {}
|
if (tab.localized) {
|
||||||
|
ref[tab.name] = { en: {} }
|
||||||
|
} else {
|
||||||
|
ref[tab.name] = {}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
callback &&
|
||||||
|
callback({
|
||||||
|
field: { ...tab, type: 'tab' },
|
||||||
|
next,
|
||||||
|
parentRef: currentParentRef,
|
||||||
|
ref: tabRef,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
tabRef = tabRef[tab.name]
|
tabRef = tabRef[tab.name]
|
||||||
|
|
||||||
if (tab.localized) {
|
if (tab.localized) {
|
||||||
@@ -132,29 +153,34 @@ export const traverseFields = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
}
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
callback &&
|
||||||
|
callback({
|
||||||
|
field: { ...tab, type: 'tab' },
|
||||||
|
next,
|
||||||
|
parentRef: currentParentRef,
|
||||||
|
ref: tabRef,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!tab.localized) {
|
||||||
callback &&
|
traverseFields({
|
||||||
callback({
|
callback,
|
||||||
field: { ...tab, type: 'tab' },
|
fields: tab.fields,
|
||||||
next,
|
fillEmpty,
|
||||||
parentRef: currentParentRef,
|
parentRef: currentParentRef,
|
||||||
ref: tabRef,
|
ref: tabRef,
|
||||||
})
|
})
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traverseFields({
|
if (skip) {
|
||||||
callback,
|
return false
|
||||||
fields: tab.fields,
|
}
|
||||||
fillEmpty,
|
|
||||||
parentRef: currentParentRef,
|
|
||||||
ref: tabRef,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -166,10 +192,18 @@ export const traverseFields = ({
|
|||||||
if (!ref[field.name]) {
|
if (!ref[field.name]) {
|
||||||
if (fillEmpty) {
|
if (fillEmpty) {
|
||||||
if (field.type === 'group') {
|
if (field.type === 'group') {
|
||||||
ref[field.name] = {}
|
if (field.localized) {
|
||||||
|
ref[field.name] = {
|
||||||
|
en: {},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ref[field.name] = {}
|
||||||
|
}
|
||||||
} else if (field.type === 'array' || field.type === 'blocks') {
|
} else if (field.type === 'array' || field.type === 'blocks') {
|
||||||
if (field.localized) {
|
if (field.localized) {
|
||||||
ref[field.name] = {}
|
ref[field.name] = {
|
||||||
|
en: [],
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ref[field.name] = []
|
ref[field.name] = []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ export const Categories: CollectionConfig = {
|
|||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
on: 'array.category',
|
on: 'array.category',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'localizedArrayPosts',
|
||||||
|
type: 'join',
|
||||||
|
collection: 'posts',
|
||||||
|
on: 'localizedArray.category',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'blocksPosts',
|
name: 'blocksPosts',
|
||||||
type: 'join',
|
type: 'join',
|
||||||
|
|||||||
@@ -104,6 +104,18 @@ export const Posts: CollectionConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'localizedArray',
|
||||||
|
type: 'array',
|
||||||
|
localized: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'category',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: categoriesSlug,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'blocks',
|
name: 'blocks',
|
||||||
type: 'blocks',
|
type: 'blocks',
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ describe('Joins Field', () => {
|
|||||||
camelCaseCategory: category.id,
|
camelCaseCategory: category.id,
|
||||||
},
|
},
|
||||||
array: [{ category: category.id }],
|
array: [{ category: category.id }],
|
||||||
|
localizedArray: [{ category: category.id }],
|
||||||
blocks: [{ blockType: 'block', category: category.id }],
|
blocks: [{ blockType: 'block', category: category.id }],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -214,6 +215,16 @@ describe('Joins Field', () => {
|
|||||||
expect(categoryWithPosts.arrayPosts.docs).toBeDefined()
|
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 () => {
|
it('should populate joins with blocks relationships', async () => {
|
||||||
const categoryWithPosts = await payload.findByID({
|
const categoryWithPosts = await payload.findByID({
|
||||||
id: category.id,
|
id: category.id,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export interface Config {
|
|||||||
'group.relatedPosts': 'posts';
|
'group.relatedPosts': 'posts';
|
||||||
'group.camelCasePosts': 'posts';
|
'group.camelCasePosts': 'posts';
|
||||||
arrayPosts: 'posts';
|
arrayPosts: 'posts';
|
||||||
|
localizedArrayPosts: 'posts';
|
||||||
blocksPosts: 'posts';
|
blocksPosts: 'posts';
|
||||||
polymorphic: 'posts';
|
polymorphic: 'posts';
|
||||||
polymorphics: 'posts';
|
polymorphics: 'posts';
|
||||||
@@ -199,6 +200,12 @@ export interface Post {
|
|||||||
id?: string | null;
|
id?: string | null;
|
||||||
}[]
|
}[]
|
||||||
| null;
|
| null;
|
||||||
|
localizedArray?:
|
||||||
|
| {
|
||||||
|
category?: (string | null) | Category;
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
blocks?:
|
blocks?:
|
||||||
| {
|
| {
|
||||||
category?: (string | null) | Category;
|
category?: (string | null) | Category;
|
||||||
@@ -272,6 +279,10 @@ export interface Category {
|
|||||||
docs?: (string | Post)[] | null;
|
docs?: (string | Post)[] | null;
|
||||||
hasNextPage?: boolean | null;
|
hasNextPage?: boolean | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
localizedArrayPosts?: {
|
||||||
|
docs?: (string | Post)[] | null;
|
||||||
|
hasNextPage?: boolean | null;
|
||||||
|
} | null;
|
||||||
blocksPosts?: {
|
blocksPosts?: {
|
||||||
docs?: (string | Post)[] | null;
|
docs?: (string | Post)[] | null;
|
||||||
hasNextPage?: boolean | null;
|
hasNextPage?: boolean | null;
|
||||||
@@ -649,6 +660,12 @@ export interface PostsSelect<T extends boolean = true> {
|
|||||||
category?: T;
|
category?: T;
|
||||||
id?: T;
|
id?: T;
|
||||||
};
|
};
|
||||||
|
localizedArray?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
category?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
blocks?:
|
blocks?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
@@ -680,6 +697,7 @@ export interface CategoriesSelect<T extends boolean = true> {
|
|||||||
camelCasePosts?: T;
|
camelCasePosts?: T;
|
||||||
};
|
};
|
||||||
arrayPosts?: T;
|
arrayPosts?: T;
|
||||||
|
localizedArrayPosts?: T;
|
||||||
blocksPosts?: T;
|
blocksPosts?: T;
|
||||||
polymorphic?: T;
|
polymorphic?: T;
|
||||||
polymorphics?: T;
|
polymorphics?: T;
|
||||||
|
|||||||
Reference in New Issue
Block a user