diff --git a/packages/db-mongodb/src/queries/buildSortParam.ts b/packages/db-mongodb/src/queries/buildSortParam.ts index 551d5bfad..94ddaf615 100644 --- a/packages/db-mongodb/src/queries/buildSortParam.ts +++ b/packages/db-mongodb/src/queries/buildSortParam.ts @@ -37,24 +37,21 @@ const relationshipSort = ({ fields, locale, path, - sort, + previousField = '', sortAggregation, - sortDirection, - versions, }: { adapter: MongooseAdapter fields: FlattenedField[] locale?: string path: string - sort: Record + 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, + ) } - if (foreignFieldPath.pathHasLocalized && locale) { - sortFieldPath = foreignFieldPath.localizedPath.replace('', locale) + if (nextPath.join('.') === 'id') { + return `${previousField}${localizedRelationshipPath}` } - const as = `__${relationshipPath.replace(/\./g, '__')}` + const as = `__${previousField}${localizedRelationshipPath}` - // 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 + sortAggregation.push({ + $lookup: { + as: `__${previousField}${localizedRelationshipPath}`, + foreignField: '_id', + from: Model.collection.name, + localField: `${previousField}${localizedRelationshipPath}`, + }, + }) - if (adapter.usePipelineInSortLookup) { - const flattenedField = `__${localField.replace(/\./g, '__')}_lookup` - sortAggregation.push({ - $addFields: { - [flattenedField]: `$${localField}`, - }, - }) - localField = flattenedField - } - - sortAggregation.push({ - $lookup: { - as, - foreignField: '_id', - from: foreignCollection.Model.collection.name, - localField, - ...(!adapter.usePipelineInSortLookup && { - pipeline: [ - { - $project: { - [sortFieldPath]: true, - }, - }, - ], - }), - }, + if (nextPath.length > 1) { + const nextRes = relationshipSort({ + adapter, + fields: collectionConfig.flattenedFields, + locale, + path: nextPath.join('.'), + previousField: `${as}.`, + sortAggregation, }) - if (adapter.usePipelineInSortLookup) { - sortAggregation.push({ - $unset: localField, - }) + if (nextRes) { + return nextRes } + + return `${as}.${nextPath.join('.')}` } - 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 + const nextField = getFieldByPath({ + fields: collectionConfig.flattenedFields, + path: nextPath[0]!, + }) + + if (nextField && nextField.pathHasLocalized && locale) { + return `${as}.${nextField.localizedPath.replace('', locale)}` } - sort[`${as}.${sortFieldPath}`] = sortDirection - return true + return `${as}.${nextPath[0]}` } } - return false + return null } export const buildSortParam = ({ @@ -217,20 +203,20 @@ export const buildSortParam = ({ return acc } - if ( - sortAggregation && - relationshipSort({ + if (sortAggregation) { + const sortRelProperty = relationshipSort({ adapter, fields, locale, path: sortProperty, - sort: acc, sortAggregation, - sortDirection, versions, }) - ) { - return acc + + if (sortRelProperty) { + acc[sortRelProperty] = sortDirection + return acc + } } const localizedProperty = getLocalizedSortProperty({ diff --git a/test/localization/int.spec.ts b/test/localization/int.spec.ts index 9ca29c336..7535e7901 100644 --- a/test/localization/int.spec.ts +++ b/test/localization/int.spec.ts @@ -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, diff --git a/test/relationships/int.spec.ts b/test/relationships/int.spec.ts index bfa0a1f25..ab36e2b26 100644 --- a/test/relationships/int.spec.ts +++ b/test/relationships/int.spec.ts @@ -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: {} }) diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index 6b853e08f..c85eea807 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -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,