Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions __tests__/server/collectionFilters.server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getTenantAndPublishedFilter } from '@/utilities/collectionFilters'

const baseProps: Parameters<typeof getTenantAndPublishedFilter>[0] = {
data: {},
req: {
headers: new Headers(),
},
relationTo: 'pages',
}

describe('getTenantAndPublishedFilter', () => {
it('adds the published status filter for page references', () => {
expect(
getTenantAndPublishedFilter({
...baseProps,
relationTo: 'pages',
}),
).toEqual({
_status: {
equals: 'published',
},
})
})

it('preserves tenant scoping when filtering page references', () => {
expect(
getTenantAndPublishedFilter({
...baseProps,
data: { tenant: 7 },
relationTo: 'posts',
}),
).toEqual({
and: [
{
tenant: {
equals: 7,
},
},
{
_status: {
equals: 'published',
},
},
],
})
})

it('does not add a draft status filter for built-in page references', () => {
expect(
getTenantAndPublishedFilter({
...baseProps,
data: { tenant: 7 },
relationTo: 'builtInPages',
}),
).toEqual({
tenant: {
equals: 7,
},
})
})
})
47 changes: 47 additions & 0 deletions __tests__/server/headerUtils.server.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
convertToNavLink,
extractAllInternalUrls,
findNavigationItemBySlug,
getCanonicalUrlsFromNavigation,
Expand Down Expand Up @@ -733,4 +734,50 @@ describe('Header Utilities', () => {
expect(topLevelNavItem({ tab: undefined, label: 'Weather' })).toEqual([])
})
})

describe('convertToNavLink', () => {
it('does not render draft page references', () => {
const result = convertToNavLink({
type: 'internal',
reference: {
relationTo: 'pages',
value: {
id: 1,
slug: 'who-we-are',
title: 'Who We Are',
tenant: 1,
layout: [],
updatedAt: '2026-07-01T00:00:00.000Z',
createdAt: '2026-07-01T00:00:00.000Z',
_status: 'draft',
},
},
})

expect(result).toBeUndefined()
})

it('renders built-in page references without draft filtering', () => {
const result = convertToNavLink({
type: 'internal',
reference: {
relationTo: 'builtInPages',
value: {
id: 1,
title: 'Forecasts',
url: '/forecasts/avalanche',
tenant: 1,
updatedAt: '2026-07-01T00:00:00.000Z',
createdAt: '2026-07-01T00:00:00.000Z',
},
},
})

expect(result).toEqual({
type: 'internal',
label: 'Forecasts',
url: '/forecasts/avalanche',
})
})
})
})
4 changes: 2 additions & 2 deletions src/fields/linkField.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { clearIrrelevantLinkValues } from '@/utilities/clearIrrelevantLinkValues'
import { getTenantFilter } from '@/utilities/collectionFilters'
import { getTenantAndPublishedFilter } from '@/utilities/collectionFilters'
import { validateExternalUrl } from '@/utilities/validateUrl'
import { Field, NamedGroupField, TextFieldSingleValidation } from 'payload'
import { text } from 'payload/shared'
Expand Down Expand Up @@ -65,7 +65,7 @@ const buildLinkFields = ({
label: 'Select page or post',
relationTo: ['pages', 'builtInPages', 'posts'],
required: true,
filterOptions: getTenantFilter,
filterOptions: getTenantAndPublishedFilter,
}

const urlField: Field = {
Expand Down
37 changes: 35 additions & 2 deletions src/utilities/collectionFilters.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { FilterOptionsProps, Where } from 'payload'
import { getTenantSlugFromCookie } from './tenancy/getTenantFromCookie'

const draftableLinkCollections = new Set(['pages', 'posts'])

type TenantFilterProps = {
data: FilterOptionsProps['data']
req: Pick<FilterOptionsProps['req'], 'headers'>
}

type TenantPublishedFilterProps = TenantFilterProps & Pick<FilterOptionsProps, 'relationTo'>
type TenantIdFilterProps = TenantFilterProps & Pick<FilterOptionsProps, 'id'>

const combineFilters = (...filters: Where[]): Where => {
const activeFilters = filters.filter((filter) => Object.keys(filter).length > 0)

if (activeFilters.length === 0) return {}
if (activeFilters.length === 1) return activeFilters[0]

return {
and: activeFilters,
}
}

export const getImageTypeFilter = () => ({
mimeType: { contains: 'image' },
})

export const getTenantFilter = ({ data, req }: FilterOptionsProps): Where => {
export const getTenantFilter = ({ data, req }: TenantFilterProps): Where => {
// If the document already has a tenant relationship set, use that
if (data.tenant) {
return {
Expand All @@ -28,7 +49,19 @@ export const getTenantFilter = ({ data, req }: FilterOptionsProps): Where => {
return {}
}

export const getTenantAndIdFilter = ({ id, data, req }: FilterOptionsProps): Where => {
export const getTenantAndPublishedFilter = (props: TenantPublishedFilterProps): Where => {
const tenantFilter = getTenantFilter(props)

if (!draftableLinkCollections.has(props.relationTo)) return tenantFilter

return combineFilters(tenantFilter, {
_status: {
equals: 'published',
},
})
}

export const getTenantAndIdFilter = ({ id, data, req }: TenantIdFilterProps): Where => {
const idFilter = { id: { not_in: [id] } }

// If the document already has a tenant relationship set, use that
Expand Down