fix(db-mongodb): support 2x and more relationship sorting (#13819)
Previously, sorting like: `sort: 'relationship.anotherRelationship.title'` (and more nested) didn't work with the MongoDB adapter, this PR fixes this.
This commit is contained in:
@@ -37,24 +37,21 @@ const relationshipSort = ({
|
||||
fields,
|
||||
locale,
|
||||
path,
|
||||
sort,
|
||||
previousField = '',
|
||||
sortAggregation,
|
||||
sortDirection,
|
||||
versions,
|
||||
}: {
|
||||
adapter: MongooseAdapter
|
||||
fields: FlattenedField[]
|
||||
locale?: string
|
||||
path: string
|
||||
sort: Record<string, string>
|
||||
previousField?: string
|
||||
sortAggregation: PipelineStage[]
|
||||
sortDirection: SortDirection
|
||||
versions?: boolean
|
||||
}) => {
|
||||
}): null | string => {
|
||||
let currentFields = fields
|
||||
const segments = path.split('.')
|
||||
if (segments.length < 2) {
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
@@ -62,98 +59,87 @@ const relationshipSort = ({
|
||||
const field = currentFields.find((each) => each.name === segment)
|
||||
|
||||
if (!field) {
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
if ('fields' in field) {
|
||||
currentFields = field.flattenedFields
|
||||
if (field.name === 'version' && versions && i === 0) {
|
||||
segments.shift()
|
||||
i--
|
||||
}
|
||||
} else if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
i !== segments.length - 1
|
||||
) {
|
||||
const relationshipPath = segments.slice(0, i + 1).join('.')
|
||||
let sortFieldPath = segments.slice(i + 1, segments.length).join('.')
|
||||
if (sortFieldPath.endsWith('.id')) {
|
||||
sortFieldPath = sortFieldPath.split('.').slice(0, -1).join('.')
|
||||
}
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
throw new APIError('Not supported')
|
||||
const nextPath = segments.slice(i + 1, segments.length)
|
||||
const relationshipFieldResult = getFieldByPath({ fields, path: relationshipPath })
|
||||
|
||||
if (
|
||||
!relationshipFieldResult ||
|
||||
!('relationTo' in relationshipFieldResult.field) ||
|
||||
typeof relationshipFieldResult.field.relationTo !== 'string'
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const foreignCollection = getCollection({ adapter, collectionSlug: field.relationTo })
|
||||
|
||||
const foreignFieldPath = getFieldByPath({
|
||||
fields: foreignCollection.collectionConfig.flattenedFields,
|
||||
path: sortFieldPath,
|
||||
const { collectionConfig, Model } = getCollection({
|
||||
adapter,
|
||||
collectionSlug: relationshipFieldResult.field.relationTo,
|
||||
})
|
||||
|
||||
if (!foreignFieldPath) {
|
||||
return false
|
||||
let localizedRelationshipPath: string = relationshipFieldResult.localizedPath
|
||||
|
||||
if (locale && relationshipFieldResult.pathHasLocalized) {
|
||||
localizedRelationshipPath = relationshipFieldResult.localizedPath.replace(
|
||||
'<locale>',
|
||||
locale,
|
||||
)
|
||||
}
|
||||
|
||||
if (foreignFieldPath.pathHasLocalized && locale) {
|
||||
sortFieldPath = foreignFieldPath.localizedPath.replace('<locale>', locale)
|
||||
if (nextPath.join('.') === 'id') {
|
||||
return `${previousField}${localizedRelationshipPath}`
|
||||
}
|
||||
|
||||
const as = `__${relationshipPath.replace(/\./g, '__')}`
|
||||
|
||||
// If we have not already sorted on this relationship yet, we need to add a lookup stage
|
||||
if (!sortAggregation.some((each) => '$lookup' in each && each.$lookup.as === as)) {
|
||||
let localField = versions ? `version.${relationshipPath}` : relationshipPath
|
||||
|
||||
if (adapter.usePipelineInSortLookup) {
|
||||
const flattenedField = `__${localField.replace(/\./g, '__')}_lookup`
|
||||
sortAggregation.push({
|
||||
$addFields: {
|
||||
[flattenedField]: `$${localField}`,
|
||||
},
|
||||
})
|
||||
localField = flattenedField
|
||||
}
|
||||
const as = `__${previousField}${localizedRelationshipPath}`
|
||||
|
||||
sortAggregation.push({
|
||||
$lookup: {
|
||||
as,
|
||||
as: `__${previousField}${localizedRelationshipPath}`,
|
||||
foreignField: '_id',
|
||||
from: foreignCollection.Model.collection.name,
|
||||
localField,
|
||||
...(!adapter.usePipelineInSortLookup && {
|
||||
pipeline: [
|
||||
{
|
||||
$project: {
|
||||
[sortFieldPath]: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
from: Model.collection.name,
|
||||
localField: `${previousField}${localizedRelationshipPath}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (adapter.usePipelineInSortLookup) {
|
||||
sortAggregation.push({
|
||||
$unset: localField,
|
||||
if (nextPath.length > 1) {
|
||||
const nextRes = relationshipSort({
|
||||
adapter,
|
||||
fields: collectionConfig.flattenedFields,
|
||||
locale,
|
||||
path: nextPath.join('.'),
|
||||
previousField: `${as}.`,
|
||||
sortAggregation,
|
||||
})
|
||||
|
||||
if (nextRes) {
|
||||
return nextRes
|
||||
}
|
||||
|
||||
return `${as}.${nextPath.join('.')}`
|
||||
}
|
||||
|
||||
const nextField = getFieldByPath({
|
||||
fields: collectionConfig.flattenedFields,
|
||||
path: nextPath[0]!,
|
||||
})
|
||||
|
||||
if (nextField && nextField.pathHasLocalized && locale) {
|
||||
return `${as}.${nextField.localizedPath.replace('<locale>', locale)}`
|
||||
}
|
||||
|
||||
return `${as}.${nextPath[0]}`
|
||||
}
|
||||
}
|
||||
|
||||
if (!adapter.usePipelineInSortLookup) {
|
||||
const lookup = sortAggregation.find(
|
||||
(each) => '$lookup' in each && each.$lookup.as === as,
|
||||
) as PipelineStage.Lookup
|
||||
const pipeline = lookup.$lookup.pipeline![0] as PipelineStage.Project
|
||||
pipeline.$project[sortFieldPath] = true
|
||||
}
|
||||
|
||||
sort[`${as}.${sortFieldPath}`] = sortDirection
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
export const buildSortParam = ({
|
||||
@@ -217,21 +203,21 @@ export const buildSortParam = ({
|
||||
return acc
|
||||
}
|
||||
|
||||
if (
|
||||
sortAggregation &&
|
||||
relationshipSort({
|
||||
if (sortAggregation) {
|
||||
const sortRelProperty = relationshipSort({
|
||||
adapter,
|
||||
fields,
|
||||
locale,
|
||||
path: sortProperty,
|
||||
sort: acc,
|
||||
sortAggregation,
|
||||
sortDirection,
|
||||
versions,
|
||||
})
|
||||
) {
|
||||
|
||||
if (sortRelProperty) {
|
||||
acc[sortRelProperty] = sortDirection
|
||||
return acc
|
||||
}
|
||||
}
|
||||
|
||||
const localizedProperty = getLocalizedSortProperty({
|
||||
config,
|
||||
|
||||
@@ -2903,7 +2903,7 @@ describe('Localization', () => {
|
||||
})
|
||||
|
||||
const req = await createLocalReq({ user }, payload)
|
||||
global.d = true
|
||||
|
||||
const res = (await copyDataFromLocaleHandler({
|
||||
fromLocale: 'en',
|
||||
req,
|
||||
@@ -2933,7 +2933,6 @@ describe('Localization', () => {
|
||||
})
|
||||
|
||||
const req = await createLocalReq({ user }, payload)
|
||||
global.d = true
|
||||
const res = (await copyDataFromLocaleHandler({
|
||||
fromLocale: 'en',
|
||||
req,
|
||||
|
||||
@@ -791,6 +791,45 @@ describe('Relationships', () => {
|
||||
expect(localized_res_2.docs).toStrictEqual([movie_1, movie_2])
|
||||
})
|
||||
|
||||
it('should sort by a property of a nested relationship', async () => {
|
||||
await payload.delete({ collection: 'directors', where: {} })
|
||||
await payload.delete({ collection: 'movies', where: {} })
|
||||
|
||||
const director = await payload.create({ collection: 'directors', data: {} })
|
||||
|
||||
const movie = await payload.create({
|
||||
collection: 'movies',
|
||||
data: { director: director.id, name: 'movie 1' },
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: 'directors',
|
||||
id: director.id,
|
||||
data: { movie: movie.id },
|
||||
})
|
||||
|
||||
const director_2 = await payload.create({ collection: 'directors', data: {} })
|
||||
|
||||
const movie_2 = await payload.create({
|
||||
collection: 'movies',
|
||||
data: { director: director_2.id, name: 'movie 2' },
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: 'directors',
|
||||
id: director_2.id,
|
||||
data: { movie: movie_2.id },
|
||||
})
|
||||
|
||||
const res = await payload.find({ collection: 'movies', sort: 'director.movie.name' })
|
||||
expect(res.docs[0].id).toBe(movie.id)
|
||||
expect(res.docs[1].id).toBe(movie_2.id)
|
||||
|
||||
const res_2 = await payload.find({ collection: 'movies', sort: '-director.movie.name' })
|
||||
expect(res_2.docs[0].id).toBe(movie_2.id)
|
||||
expect(res_2.docs[1].id).toBe(movie.id)
|
||||
})
|
||||
|
||||
it('should sort by multiple properties of a relationship', async () => {
|
||||
await payload.delete({ collection: 'directors', where: {} })
|
||||
await payload.delete({ collection: 'movies', where: {} })
|
||||
|
||||
@@ -368,7 +368,6 @@ describe('Versions', () => {
|
||||
|
||||
it('should allow to create with a localized relationships inside a localized array and a block', async () => {
|
||||
const post = await payload.create({ collection: 'posts', data: {} })
|
||||
global.d = true
|
||||
const res = await payload.create({
|
||||
collection: 'localized-posts',
|
||||
draft: true,
|
||||
|
||||
Reference in New Issue
Block a user