feat: join field defaultLimit and defaultSort (#8908)
### What?
Allow specifying the defaultSort and defaultLimit to use for populating
a join field
### Why?
It is much easier to set defaults rather than be forced to always call
the join query using the query pattern ("?joins[categories][limit]=0").
### How?
See docs and type changes
This commit is contained in:
@@ -6,8 +6,10 @@ desc: The Join field provides the ability to work on related documents. Learn ho
|
||||
keywords: join, relationship, junction, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can edit and view collections
|
||||
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is stored on the collection with a Join
|
||||
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can
|
||||
edit and view collections
|
||||
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is
|
||||
stored on the collection with a Join
|
||||
field. Instead, the Admin UI surfaces the related documents for a better editing experience and is surfaced by Payload's
|
||||
APIs.
|
||||
|
||||
@@ -111,9 +113,11 @@ related docs from a new pseudo-junction collection called `categories_posts`. No
|
||||
third junction collection, and can be surfaced on both Posts and Categories. But, importantly, you could add
|
||||
additional "context" fields to this shared junction collection.
|
||||
|
||||
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add custom "context" fields like `featured` or `spotlight`,
|
||||
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add
|
||||
custom "context" fields like `featured` or `spotlight`,
|
||||
which would allow you to store additional information directly on relationships.
|
||||
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a powerful Admin UI.
|
||||
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a
|
||||
powerful Admin UI.
|
||||
|
||||
## Config Options
|
||||
|
||||
@@ -126,11 +130,11 @@ The `join` field gives you complete control over any type of relational architec
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
|
||||
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. |
|
||||
| **`defaultSort`** | The field name used to specify the order the joined documents are returned. |
|
||||
| **`admin`** | Admin-specific configuration. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
@@ -150,12 +154,12 @@ object with:
|
||||
{
|
||||
"id": "66e3431a3f23e684075aaeb9",
|
||||
// other fields...
|
||||
"category": "66e3431a3f23e684075aae9c",
|
||||
},
|
||||
"category": "66e3431a3f23e684075aae9c"
|
||||
}
|
||||
// { ... }
|
||||
],
|
||||
"hasNextPage": false
|
||||
},
|
||||
}
|
||||
// other fields...
|
||||
}
|
||||
```
|
||||
@@ -213,7 +217,8 @@ You can specify as many `joins` parameters as needed for the same or different j
|
||||
|
||||
### GraphQL
|
||||
|
||||
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each join field in your query.
|
||||
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each
|
||||
join field in your query.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ export const buildJoinAggregation = async ({
|
||||
const joinModel = adapter.collections[join.field.collection]
|
||||
|
||||
const {
|
||||
limit: limitJoin = 10,
|
||||
sort: sortJoin,
|
||||
limit: limitJoin = join.field.defaultLimit ?? 10,
|
||||
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
|
||||
where: whereJoin,
|
||||
} = joins?.[join.schemaPath] || {}
|
||||
|
||||
@@ -66,7 +66,7 @@ export const buildJoinAggregation = async ({
|
||||
config: adapter.payload.config,
|
||||
fields: adapter.payload.collections[slug].config.fields,
|
||||
locale,
|
||||
sort: sortJoin || collectionConfig.defaultSort,
|
||||
sort: sortJoin,
|
||||
timestamps: true,
|
||||
})
|
||||
const sortProperty = Object.keys(sort)[0]
|
||||
|
||||
@@ -238,8 +238,8 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
const {
|
||||
limit: limitArg = 10,
|
||||
sort,
|
||||
limit: limitArg = field.defaultLimit ?? 10,
|
||||
sort = field.defaultSort,
|
||||
where,
|
||||
} = joinQuery[`${path.replaceAll('_', '.')}${field.name}`] || {}
|
||||
let limit = limitArg
|
||||
@@ -285,7 +285,9 @@ export const traverseFields = ({
|
||||
let columnReferenceToCurrentID: string
|
||||
|
||||
if (versions) {
|
||||
columnReferenceToCurrentID = `${topLevelTableName.replace('_', '').replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id`
|
||||
columnReferenceToCurrentID = `${topLevelTableName
|
||||
.replace('_', '')
|
||||
.replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id`
|
||||
} else {
|
||||
columnReferenceToCurrentID = `${topLevelTableName}_id`
|
||||
}
|
||||
|
||||
@@ -420,7 +420,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
}
|
||||
|
||||
if (field.type === 'join') {
|
||||
const { limit = 10 } = joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {}
|
||||
const { limit = field.defaultLimit ?? 10 } =
|
||||
joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {}
|
||||
|
||||
// raw hasMany results from SQLite
|
||||
if (typeof fieldData === 'string') {
|
||||
|
||||
@@ -121,6 +121,7 @@ import type {
|
||||
JSONFieldValidation,
|
||||
PointFieldValidation,
|
||||
RadioFieldValidation,
|
||||
Sort,
|
||||
TextareaFieldValidation,
|
||||
} from '../../index.js'
|
||||
import type { DocumentPreferences } from '../../preferences/types.js'
|
||||
@@ -1452,6 +1453,8 @@ export type JoinField = {
|
||||
* The slug of the collection to relate with.
|
||||
*/
|
||||
collection: CollectionSlug
|
||||
defaultLimit?: number
|
||||
defaultSort?: Sort
|
||||
defaultValue?: never
|
||||
/**
|
||||
* This does not need to be set and will be overridden by the relationship field's hasMany property.
|
||||
|
||||
@@ -47,7 +47,10 @@ export const Categories: CollectionConfig = {
|
||||
label: 'Related Posts',
|
||||
type: 'join',
|
||||
collection: postsSlug,
|
||||
defaultSort: '-title',
|
||||
defaultLimit: 5,
|
||||
on: 'category',
|
||||
maxDepth: 1,
|
||||
},
|
||||
{
|
||||
name: 'hasManyPosts',
|
||||
|
||||
@@ -105,15 +105,6 @@ describe('Joins Field', () => {
|
||||
},
|
||||
collection: 'categories',
|
||||
})
|
||||
// const sortCategoryWithPosts = await payload.findByID({
|
||||
// id: category.id,
|
||||
// joins: {
|
||||
// 'group.relatedPosts': {
|
||||
// sort: 'title',
|
||||
// },
|
||||
// },
|
||||
// collection: 'categories',
|
||||
// })
|
||||
|
||||
expect(categoryWithPosts.group.relatedPosts.docs).toHaveLength(10)
|
||||
expect(categoryWithPosts.group.relatedPosts.docs[0]).toHaveProperty('id')
|
||||
@@ -125,11 +116,12 @@ describe('Joins Field', () => {
|
||||
const { docs } = await payload.find({
|
||||
limit: 1,
|
||||
collection: 'posts',
|
||||
depth: 2,
|
||||
})
|
||||
|
||||
expect(docs[0].category.id).toBeDefined()
|
||||
expect(docs[0].category.name).toBeDefined()
|
||||
expect(docs[0].category.relatedPosts.docs).toHaveLength(10)
|
||||
expect(docs[0].category.relatedPosts.docs).toHaveLength(5) // uses defaultLimit
|
||||
})
|
||||
|
||||
it('should populate relationships in joins with camelCase names', async () => {
|
||||
|
||||
Reference in New Issue
Block a user