feat: join field with polymorphic relationships (#9990)

### What?
The join field had a limitation imposed that prevents it from targeting
polymorphic relationship fields. With this change we can support any
relationship fields.

### Why?
Improves the functionality of join field.

### How?
Extended the database adapters and removed the config sanitization that
would throw an error when polymorphic relationships were used.

Fixes #
This commit is contained in:
Dan Ribbens
2024-12-19 17:34:52 -05:00
committed by GitHub
parent 07be617963
commit d03658de01
19 changed files with 330 additions and 40 deletions

View File

@@ -115,6 +115,30 @@ export const Categories: CollectionConfig = {
collection: 'posts',
on: 'blocks.category',
},
{
name: 'polymorphic',
type: 'join',
collection: postsSlug,
on: 'polymorphic',
},
{
name: 'polymorphics',
type: 'join',
collection: postsSlug,
on: 'polymorphics',
},
{
name: 'localizedPolymorphic',
type: 'join',
collection: postsSlug,
on: 'localizedPolymorphic',
},
{
name: 'localizedPolymorphics',
type: 'join',
collection: postsSlug,
on: 'localizedPolymorphics',
},
{
name: 'singulars',
type: 'join',

View File

@@ -53,6 +53,30 @@ export const Posts: CollectionConfig = {
hasMany: true,
localized: true,
},
{
name: 'polymorphic',
type: 'relationship',
relationTo: ['categories', 'users'],
},
{
name: 'polymorphics',
type: 'relationship',
relationTo: ['categories', 'users'],
hasMany: true,
},
{
name: 'localizedPolymorphic',
type: 'relationship',
relationTo: ['categories', 'users'],
localized: true,
},
{
name: 'localizedPolymorphics',
type: 'relationship',
relationTo: ['categories', 'users'],
hasMany: true,
localized: true,
},
{
name: 'group',
type: 'group',

View File

@@ -291,6 +291,67 @@ test.describe('Join Field', () => {
await expect(joinField.locator('tbody .row-1')).toContainText('Test Post 1 Updated')
})
test('should create join collection from polymorphic relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-polymorphic.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-polymorphic')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic, hasMany relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-polymorphics.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-polymorphics')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic localized relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-localizedPolymorphic.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-localizedPolymorphic')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic, hasMany, localized relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-localizedPolymorphics.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-localizedPolymorphics')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should render empty relationship table when creating new document', async () => {
await page.goto(categoriesURL.create)
const joinField = page.locator('#field-relatedPosts.field-type.join')

View File

@@ -90,6 +90,26 @@ describe('Joins Field', () => {
upload: uploadedImage,
categories,
categoriesLocalized: categories,
polymorphic: {
relationTo: 'categories',
value: category.id,
},
polymorphics: [
{
relationTo: 'categories',
value: category.id,
},
],
localizedPolymorphic: {
relationTo: 'categories',
value: category.id,
},
localizedPolymorphics: [
{
relationTo: 'categories',
value: category.id,
},
],
group: {
category: category.id,
camelCaseCategory: category.id,
@@ -216,6 +236,17 @@ describe('Joins Field', () => {
expect(docs[0].upload.relatedPosts.docs).toHaveLength(10)
})
it('should join on polymorphic relationships', async () => {
const categoryWithPosts = await payload.findByID({
collection: categoriesSlug,
id: category.id,
})
expect(categoryWithPosts.polymorphic.docs[0]).toHaveProperty('id')
expect(categoryWithPosts.polymorphics.docs[0]).toHaveProperty('id')
expect(categoryWithPosts.localizedPolymorphic.docs[0]).toHaveProperty('id')
expect(categoryWithPosts.localizedPolymorphics.docs[0]).toHaveProperty('id')
})
it('should filter joins using where query', async () => {
const categoryWithPosts = await payload.findByID({
id: category.id,

View File

@@ -38,6 +38,10 @@ export interface Config {
'group.camelCasePosts': 'posts';
arrayPosts: 'posts';
blocksPosts: 'posts';
polymorphic: 'posts';
polymorphics: 'posts';
localizedPolymorphic: 'posts';
localizedPolymorphics: 'posts';
filtered: 'posts';
hiddenPosts: 'hidden-posts';
singulars: 'singular';
@@ -123,6 +127,48 @@ export interface Post {
category?: (string | null) | Category;
categories?: (string | Category)[] | null;
categoriesLocalized?: (string | Category)[] | null;
polymorphic?:
| ({
relationTo: 'categories';
value: string | Category;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
polymorphics?:
| (
| {
relationTo: 'categories';
value: string | Category;
}
| {
relationTo: 'users';
value: string | User;
}
)[]
| null;
localizedPolymorphic?:
| ({
relationTo: 'categories';
value: string | Category;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
localizedPolymorphics?:
| (
| {
relationTo: 'categories';
value: string | Category;
}
| {
relationTo: 'users';
value: string | User;
}
)[]
| null;
group?: {
category?: (string | null) | Category;
camelCaseCategory?: (string | null) | Category;
@@ -207,6 +253,22 @@ export interface Category {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
polymorphic?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
polymorphics?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
localizedPolymorphic?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
localizedPolymorphics?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
singulars?: {
docs?: (string | Singular)[] | null;
hasNextPage?: boolean | null;
@@ -239,6 +301,23 @@ export interface Singular {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "versions".
@@ -347,23 +426,6 @@ export interface RestrictedPost {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
@@ -481,6 +543,10 @@ export interface PostsSelect<T extends boolean = true> {
category?: T;
categories?: T;
categoriesLocalized?: T;
polymorphic?: T;
polymorphics?: T;
localizedPolymorphic?: T;
localizedPolymorphics?: T;
group?:
| T
| {
@@ -525,6 +591,10 @@ export interface CategoriesSelect<T extends boolean = true> {
};
arrayPosts?: T;
blocksPosts?: T;
polymorphic?: T;
polymorphics?: T;
localizedPolymorphic?: T;
localizedPolymorphics?: T;
singulars?: T;
filtered?: T;
updatedAt?: T;