feat: prevent querying relationship when filterOptions returns false (#4392)

fix: hidden collections showing in lexical and slate relationships
feat: prevent querying relationship when filterOptions returns false
fix: hidden collections appear in richtext internal link options

Co-authored-by: Alessio Gravili <70709113+AlessioGr@users.noreply.github.com>
This commit is contained in:
Dan Ribbens
2023-12-15 12:43:43 -05:00
committed by GitHub
parent c49fd66922
commit c1bd338d0d
15 changed files with 336 additions and 159 deletions

View File

@@ -1,6 +1,8 @@
export const slug = 'fields-relationship'
export const relationOneSlug = 'relation-one'
export const relationTrueFilterOptionSlug = 'relation-filter-true'
export const relationFalseFilterOptionSlug = 'relation-filter-false'
export const relationTwoSlug = 'relation-two'
export const relationRestrictedSlug = 'relation-restricted'
export const relationWithTitleSlug = 'relation-with-title'

View File

@@ -8,8 +8,10 @@ import { PrePopulateFieldUI } from './PrePopulateFieldUI'
import {
collection1Slug,
collection2Slug,
relationFalseFilterOptionSlug,
relationOneSlug,
relationRestrictedSlug,
relationTrueFilterOptionSlug,
relationTwoSlug,
relationUpdatedExternallySlug,
relationWithTitleSlug,
@@ -32,6 +34,7 @@ export interface RelationOne {
id: string
name: string
}
export type RelationTwo = RelationOne
export type RelationRestricted = RelationOne
export type RelationWithTitle = RelationOne
@@ -46,7 +49,6 @@ const baseRelationshipFields: CollectionConfig['fields'] = [
export default buildConfigWithDefaults({
collections: [
{
slug,
admin: {
defaultColumns: [
'id',
@@ -58,41 +60,39 @@ export default buildConfigWithDefaults({
},
fields: [
{
type: 'relationship',
name: 'relationship',
relationTo: relationOneSlug,
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipHasMany',
relationTo: relationOneSlug,
hasMany: true,
relationTo: relationOneSlug,
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipMultiple',
relationTo: [relationOneSlug, relationTwoSlug],
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipHasManyMultiple',
hasMany: true,
relationTo: [relationOneSlug, relationTwoSlug],
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipRestricted',
relationTo: relationRestrictedSlug,
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipWithTitle',
relationTo: relationWithTitleSlug,
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipFiltered',
relationTo: relationOneSlug,
filterOptions: (args: FilterOptionsProps<FieldsRelationship>) => {
return {
id: {
@@ -100,11 +100,11 @@ export default buildConfigWithDefaults({
},
}
},
relationTo: relationOneSlug,
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipFilteredAsync',
relationTo: relationOneSlug,
filterOptions: async (args: FilterOptionsProps<FieldsRelationship>) => {
return {
id: {
@@ -112,57 +112,84 @@ export default buildConfigWithDefaults({
},
}
},
relationTo: relationOneSlug,
type: 'relationship',
},
{
type: 'relationship',
name: 'relationshipManyFiltered',
relationTo: [relationWithTitleSlug, relationOneSlug],
hasMany: true,
filterOptions: ({ relationTo, siblingData }: any) => {
if (relationTo === relationOneSlug) {
return { name: { equals: 'include' } }
}
if (relationTo === relationTrueFilterOptionSlug) {
return true
}
if (relationTo === relationFalseFilterOptionSlug) {
return false
}
if (siblingData.filter) {
return { name: { contains: siblingData.filter } }
}
return { and: [] }
},
hasMany: true,
relationTo: [
relationWithTitleSlug,
relationFalseFilterOptionSlug,
relationTrueFilterOptionSlug,
relationOneSlug,
],
type: 'relationship',
},
{
type: 'text',
name: 'filter',
type: 'text',
},
{
name: 'relationshipReadOnly',
type: 'relationship',
relationTo: relationOneSlug,
admin: {
readOnly: true,
},
relationTo: relationOneSlug,
type: 'relationship',
},
],
slug,
},
{
slug: relationOneSlug,
fields: baseRelationshipFields,
},
{
slug: relationTwoSlug,
fields: baseRelationshipFields,
},
{
slug: relationRestrictedSlug,
admin: {
useAsTitle: 'name',
},
fields: baseRelationshipFields,
access: {
read: () => false,
create: () => false,
},
slug: relationFalseFilterOptionSlug,
},
{
admin: {
useAsTitle: 'name',
},
fields: baseRelationshipFields,
slug: relationTrueFilterOptionSlug,
},
{
fields: baseRelationshipFields,
slug: relationOneSlug,
},
{
fields: baseRelationshipFields,
slug: relationTwoSlug,
},
{
access: {
create: () => false,
read: () => false,
},
admin: {
useAsTitle: 'name',
},
fields: baseRelationshipFields,
slug: relationRestrictedSlug,
},
{
slug: relationWithTitleSlug,
admin: {
useAsTitle: 'meta.title',
},
@@ -170,7 +197,6 @@ export default buildConfigWithDefaults({
...baseRelationshipFields,
{
name: 'meta',
type: 'group',
fields: [
{
name: 'title',
@@ -178,110 +204,112 @@ export default buildConfigWithDefaults({
type: 'text',
},
],
type: 'group',
},
],
slug: relationWithTitleSlug,
},
{
slug: relationUpdatedExternallySlug,
admin: {
useAsTitle: 'name',
},
fields: [
{
type: 'row',
fields: [
{
name: 'relationPrePopulate',
type: 'relationship',
relationTo: collection1Slug,
admin: {
width: '75%',
},
relationTo: collection1Slug,
type: 'relationship',
},
{
type: 'ui',
name: 'prePopulate',
admin: {
width: '25%',
components: {
Field: () => PrePopulateFieldUI({ path: 'relationPrePopulate', hasMany: false }),
Field: () => PrePopulateFieldUI({ hasMany: false, path: 'relationPrePopulate' }),
},
width: '25%',
},
type: 'ui',
},
],
type: 'row',
},
{
type: 'row',
fields: [
{
name: 'relationHasMany',
type: 'relationship',
relationTo: collection1Slug,
hasMany: true,
admin: {
width: '75%',
},
hasMany: true,
relationTo: collection1Slug,
type: 'relationship',
},
{
type: 'ui',
name: 'prePopulateRelationHasMany',
admin: {
width: '25%',
components: {
Field: () =>
PrePopulateFieldUI({ path: 'relationHasMany', hasMultipleRelations: false }),
PrePopulateFieldUI({ hasMultipleRelations: false, path: 'relationHasMany' }),
},
width: '25%',
},
type: 'ui',
},
],
type: 'row',
},
{
type: 'row',
fields: [
{
name: 'relationToManyHasMany',
type: 'relationship',
relationTo: [collection1Slug, collection2Slug],
hasMany: true,
admin: {
width: '75%',
},
hasMany: true,
relationTo: [collection1Slug, collection2Slug],
type: 'relationship',
},
{
type: 'ui',
name: 'prePopulateToMany',
admin: {
width: '25%',
components: {
Field: () =>
PrePopulateFieldUI({
path: 'relationToManyHasMany',
hasMultipleRelations: true,
path: 'relationToManyHasMany',
}),
},
width: '25%',
},
type: 'ui',
},
],
type: 'row',
},
],
slug: relationUpdatedExternallySlug,
},
{
fields: [
{
name: 'name',
type: 'text',
},
],
slug: collection1Slug,
fields: [
{
type: 'text',
name: 'name',
},
],
},
{
slug: collection2Slug,
fields: [
{
type: 'text',
name: 'name',
type: 'text',
},
],
slug: collection2Slug,
},
],
onInit: async (payload) => {
@@ -358,11 +386,11 @@ export default buildConfigWithDefaults({
collection: slug,
data: {
relationship: relationOneDocId,
relationshipRestricted: restrictedDocId,
relationshipHasManyMultiple: relationOneIDs.map((id) => ({
relationTo: relationOneSlug,
value: id,
})),
relationshipRestricted: restrictedDocId,
},
})
})
@@ -374,10 +402,10 @@ export default buildConfigWithDefaults({
collection: slug,
data: {
relationship: relationOneDocId,
relationshipRestricted: restrictedDocId,
relationshipHasMany: [relationOneID],
relationshipHasManyMultiple: [{ relationTo: relationTwoSlug, value: relationTwoID }],
relationshipReadOnly: relationOneID,
relationshipRestricted: restrictedDocId,
},
})
})

View File

@@ -17,8 +17,10 @@ import { initPageConsoleErrorCatch, openDocControls, saveDocAndAssert } from '..
import { AdminUrlUtil } from '../helpers/adminUrlUtil'
import { initPayloadE2E } from '../helpers/configHelpers'
import {
relationFalseFilterOptionSlug,
relationOneSlug,
relationRestrictedSlug,
relationTrueFilterOptionSlug,
relationTwoSlug,
relationUpdatedExternallySlug,
relationWithTitleSlug,
@@ -112,9 +114,9 @@ describe('fields - relationship', () => {
data: {
name: 'with-existing-relations',
relationship: relationOneDoc.id,
relationshipReadOnly: relationOneDoc.id,
relationshipRestricted: restrictedRelation.id,
relationshipWithTitle: relationWithTitle.id,
relationshipReadOnly: relationOneDoc.id,
},
})) as any
})
@@ -322,6 +324,41 @@ describe('fields - relationship', () => {
await expect(options).not.toContainText('exclude')
})
test('should not query for a relationship when filterOptions returns false', async () => {
await payload.create({
collection: relationFalseFilterOptionSlug,
data: {
name: 'whatever',
},
})
await page.goto(url.create)
// select relationshipMany field that relies on siblingData field above
await page.locator('#field-relationshipManyFiltered .rs__control').click()
const options = page.locator('#field-relationshipManyFiltered .rs__menu')
await expect(options).toContainText('Relation With Titles')
await expect(options).not.toContainText('whatever')
})
test('should show a relationship when filterOptions returns true', async () => {
await payload.create({
collection: relationTrueFilterOptionSlug,
data: {
name: 'truth',
},
})
await page.goto(url.create)
// select relationshipMany field that relies on siblingData field above
await page.locator('#field-relationshipManyFiltered .rs__control').click()
const options = page.locator('#field-relationshipManyFiltered .rs__menu')
await expect(options).toContainText('truth')
})
test('should open document drawer from read-only relationships', async () => {
await page.goto(url.edit(docWithExistingRelations.id))
@@ -492,6 +529,6 @@ async function clearCollectionDocs(collectionSlug: string): Promise<void> {
(doc) => doc.id,
)
await mapAsync(ids, async (id) => {
await payload.delete({ collection: collectionSlug, id })
await payload.delete({ id, collection: collectionSlug })
})
}