Skip to content

Commit 409dd56

Browse files
fix(plugin-multi-tenant): autosave global documents not rendering (#13552)
Fixes #13507 When enabling autosave on global multi-tenant documents, the page would not always render - more noticeably with smaller autosave intervals.
1 parent 5c16443 commit 409dd56

File tree

8 files changed

+238
-55
lines changed

8 files changed

+238
-55
lines changed

packages/plugin-multi-tenant/src/utilities/getGlobalViewRedirect.ts

Lines changed: 109 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Args = {
2525
view: ViewTypes
2626
}
2727
export async function getGlobalViewRedirect({
28-
slug,
28+
slug: collectionSlug,
2929
basePath,
3030
docID,
3131
headers,
@@ -64,45 +64,67 @@ export async function getGlobalViewRedirect({
6464
tenant = tenantOptions[0]?.value || null
6565
}
6666

67-
try {
68-
const { docs } = await payload.find({
69-
collection: slug,
70-
depth: 0,
71-
limit: 1,
72-
overrideAccess: false,
73-
pagination: false,
74-
user,
75-
where: {
76-
[tenantFieldName]: {
77-
equals: tenant,
67+
if (tenant) {
68+
try {
69+
const globalTenantDocQuery = await payload.find({
70+
collection: collectionSlug,
71+
depth: 0,
72+
limit: 1,
73+
pagination: false,
74+
select: {
75+
id: true,
7876
},
79-
},
80-
})
77+
where: {
78+
[tenantFieldName]: {
79+
equals: tenant,
80+
},
81+
},
82+
})
8183

82-
const tenantDocID = docs?.[0]?.id
84+
const globalTenantDocID = globalTenantDocQuery?.docs?.[0]?.id
8385

84-
if (view === 'document') {
85-
if (docID && !tenantDocID) {
86-
// viewing a document with an id but does not match the selected tenant, redirect to create route
87-
redirectRoute = `/collections/${slug}/create`
88-
} else if (tenantDocID && docID !== tenantDocID) {
89-
// tenant document already exists but does not match current route doc ID, redirect to matching tenant doc
90-
redirectRoute = `/collections/${slug}/${tenantDocID}`
91-
}
92-
} else if (view === 'list') {
93-
if (tenantDocID) {
94-
// tenant document exists, redirect to edit view
95-
redirectRoute = `/collections/${slug}/${tenantDocID}`
96-
} else {
97-
// tenant document does not exist, redirect to create route
98-
redirectRoute = `/collections/${slug}/create`
86+
if (view === 'document') {
87+
// global tenant document edit view
88+
if (globalTenantDocID && docID !== globalTenantDocID) {
89+
// tenant document already exists but does not match current route docID
90+
// redirect to matching tenant docID from query
91+
redirectRoute = `/collections/${collectionSlug}/${globalTenantDocID}`
92+
} else if (docID && !globalTenantDocID) {
93+
// a docID was found in the route but no global document with this tenant exists
94+
// so we need to generate a redirect to the create route
95+
redirectRoute = await generateCreateRedirect({
96+
collectionSlug,
97+
payload,
98+
tenantID: tenant,
99+
})
100+
}
101+
} else if (view === 'list') {
102+
// global tenant document list view
103+
if (globalTenantDocID) {
104+
// tenant document exists, redirect from list view to the document edit view
105+
redirectRoute = `/collections/${collectionSlug}/${globalTenantDocID}`
106+
} else {
107+
// no matching document was found for the current tenant
108+
// so we need to generate a redirect to the create route
109+
redirectRoute = await generateCreateRedirect({
110+
collectionSlug,
111+
payload,
112+
tenantID: tenant,
113+
})
114+
}
99115
}
116+
} catch (e: unknown) {
117+
const prefix = `${e && typeof e === 'object' && 'message' in e && typeof e.message === 'string' ? `${e.message} - ` : ''}`
118+
payload.logger.error(e, `${prefix}Multi Tenant Redirect Error`)
100119
}
101-
} catch (e: unknown) {
102-
payload.logger.error(
103-
e,
104-
`${typeof e === 'object' && e && 'message' in e ? `e?.message - ` : ''}Multi Tenant Redirect Error`,
105-
)
120+
} else {
121+
// no tenants were found, redirect to the admin view
122+
return formatAdminURL({
123+
adminRoute: payload.config.routes.admin,
124+
basePath,
125+
path: '',
126+
serverURL: payload.config.serverURL,
127+
})
106128
}
107129

108130
if (redirectRoute) {
@@ -114,5 +136,57 @@ export async function getGlobalViewRedirect({
114136
})
115137
}
116138

139+
// no redirect is needed
140+
// the current route is valid
117141
return undefined
118142
}
143+
144+
type GenerateCreateArgs = {
145+
collectionSlug: string
146+
payload: Payload
147+
tenantID: number | string
148+
}
149+
/**
150+
* Generate a redirect URL for creating a new document in a multi-tenant collection.
151+
*
152+
* If autosave is enabled on the collection, we need to create the document and then redirect to it.
153+
* Otherwise we can redirect to the default create route.
154+
*/
155+
async function generateCreateRedirect({
156+
collectionSlug,
157+
payload,
158+
tenantID,
159+
}: GenerateCreateArgs): Promise<`/${string}` | undefined> {
160+
const collection = payload.collections[collectionSlug]
161+
if (
162+
collection?.config.versions?.drafts &&
163+
typeof collection.config.versions.drafts === 'object' &&
164+
collection.config.versions.drafts.autosave
165+
) {
166+
// Autosave is enabled, create a document first
167+
try {
168+
const doc = await payload.create({
169+
collection: collectionSlug,
170+
data: {
171+
tenant: tenantID,
172+
},
173+
depth: 0,
174+
draft: true,
175+
select: {
176+
id: true,
177+
},
178+
})
179+
return `/collections/${collectionSlug}/${doc.id}`
180+
} catch (error) {
181+
payload.logger.error(
182+
error,
183+
`Error creating autosave global multi tenant document for ${collectionSlug}`,
184+
)
185+
}
186+
187+
return '/'
188+
}
189+
190+
// Autosave is not enabled, redirect to default create route
191+
return `/collections/${collectionSlug}/create`
192+
}

packages/plugin-multi-tenant/src/utilities/getTenantOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const getTenantOptions = async ({
3535
overrideAccess: false,
3636
select: {
3737
[useAsTitle]: true,
38-
...(isOrderable ? { _order: true } : {}),
38+
...(isOrderable && { _order: true }),
3939
},
4040
sort: isOrderable ? '_order' : useAsTitle,
4141
user,

test/helpers/e2e/selectInput.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,22 +96,26 @@ async function selectOption({
9696
type GetSelectInputValueFunction = <TMultiSelect = true>(args: {
9797
multiSelect: TMultiSelect
9898
selectLocator: Locator
99+
valueLabelClass?: string
99100
}) => Promise<TMultiSelect extends true ? string[] : string | undefined>
100101

101102
export const getSelectInputValue: GetSelectInputValueFunction = async ({
102103
selectLocator,
103104
multiSelect = false,
105+
valueLabelClass,
104106
}) => {
105107
if (multiSelect) {
106108
// For multi-select, get all selected options
107109
const selectedOptions = await selectLocator
108-
.locator('.multi-value-label__text')
110+
.locator(valueLabelClass || '.multi-value-label__text')
109111
.allTextContents()
110112
return selectedOptions || []
111113
}
112114

113115
// For single-select, get the selected value
114-
const singleValue = await selectLocator.locator('.react-select--single-value').textContent()
116+
const singleValue = await selectLocator
117+
.locator(valueLabelClass || '.react-select--single-value')
118+
.textContent()
115119
return (singleValue ?? undefined) as any
116120
}
117121

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
import { autosaveGlobalSlug } from '../shared.js'
4+
5+
export const AutosaveGlobal: CollectionConfig = {
6+
slug: autosaveGlobalSlug,
7+
admin: {
8+
useAsTitle: 'title',
9+
group: 'Tenant Globals',
10+
},
11+
fields: [
12+
{
13+
name: 'title',
14+
label: 'Title',
15+
type: 'text',
16+
required: true,
17+
},
18+
{
19+
name: 'description',
20+
type: 'textarea',
21+
},
22+
],
23+
versions: {
24+
drafts: {
25+
autosave: {
26+
interval: 100,
27+
},
28+
},
29+
},
30+
}

test/plugin-multi-tenant/config.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ const dirname = path.dirname(filename)
77
import type { Config as ConfigType } from './payload-types.js'
88

99
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
10+
import { AutosaveGlobal } from './collections/AutosaveGlobal.js'
1011
import { Menu } from './collections/Menu.js'
1112
import { MenuItems } from './collections/MenuItems.js'
1213
import { Tenants } from './collections/Tenants.js'
1314
import { Users } from './collections/Users/index.js'
1415
import { seed } from './seed/index.js'
15-
import { menuItemsSlug, menuSlug } from './shared.js'
16+
import { autosaveGlobalSlug, menuItemsSlug, menuSlug } from './shared.js'
1617

1718
export default buildConfigWithDefaults({
18-
collections: [Tenants, Users, MenuItems, Menu],
19+
collections: [Tenants, Users, MenuItems, Menu, AutosaveGlobal],
1920
admin: {
2021
autoLogin: false,
2122
importMap: {
@@ -44,6 +45,9 @@ export default buildConfigWithDefaults({
4445
[menuSlug]: {
4546
isGlobal: true,
4647
},
48+
[autosaveGlobalSlug]: {
49+
isGlobal: true,
50+
},
4751
},
4852
i18n: {
4953
translations: {

0 commit comments

Comments
 (0)