Skip to content

Commit 62744e7

Browse files
authored
fix(next, payload): enable relationship & upload version tracking when localization enabled (#7508)
1 parent e8bed7b commit 62744e7

File tree

9 files changed

+237
-43
lines changed

9 files changed

+237
-43
lines changed

.vscode/launch.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@
5656
"request": "launch",
5757
"type": "node-terminal"
5858
},
59+
{
60+
"command": "node --no-deprecation test/dev.js fields-relationship",
61+
"cwd": "${workspaceFolder}",
62+
"name": "Run Dev Fields-Relationship",
63+
"request": "launch",
64+
"type": "node-terminal"
65+
},
5966
{
6067
"command": "node --no-deprecation test/dev.js login-with-username",
6168
"cwd": "${workspaceFolder}",

packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/index.tsx

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ const generateLabelFromValue = (
2626
locale: string,
2727
value: { relationTo: string; value: RelationshipValue } | RelationshipValue,
2828
): string => {
29-
let relation: string
29+
if (Array.isArray(value)) {
30+
return value
31+
.map((v) => generateLabelFromValue(collections, field, locale, v))
32+
.filter(Boolean) // Filters out any undefined or empty values
33+
.join(', ')
34+
}
35+
3036
let relatedDoc: RelationshipValue
3137
let valueToReturn = '' as any
3238

@@ -37,17 +43,20 @@ const generateLabelFromValue = (
3743
return String(value)
3844
}
3945

40-
if (Array.isArray(relationTo)) {
41-
if (typeof value === 'object') {
42-
relation = value.relationTo
43-
relatedDoc = value.value
44-
}
46+
if (typeof value === 'object' && 'relationTo' in value) {
47+
relatedDoc = value.value
4548
} else {
46-
relation = relationTo
49+
// Non-polymorphic relationship
4750
relatedDoc = value
4851
}
4952

50-
const relatedCollection = collections.find((c) => c.slug === relation)
53+
const relatedCollection = relationTo
54+
? collections.find(
55+
(c) =>
56+
c.slug ===
57+
(typeof value === 'object' && 'relationTo' in value ? value.relationTo : relationTo),
58+
)
59+
: null
5160

5261
if (relatedCollection) {
5362
const useAsTitle = relatedCollection?.admin?.useAsTitle
@@ -56,45 +65,65 @@ const generateLabelFromValue = (
5665
)
5766
let titleFieldIsLocalized = false
5867

59-
if (useAsTitleField && fieldAffectsData(useAsTitleField))
68+
if (useAsTitleField && fieldAffectsData(useAsTitleField)) {
6069
titleFieldIsLocalized = useAsTitleField.localized
70+
}
6171

6272
if (typeof relatedDoc?.[useAsTitle] !== 'undefined') {
6373
valueToReturn = relatedDoc[useAsTitle]
6474
} else if (typeof relatedDoc?.id !== 'undefined') {
6575
valueToReturn = relatedDoc.id
76+
} else {
77+
valueToReturn = relatedDoc
6678
}
6779

6880
if (typeof valueToReturn === 'object' && titleFieldIsLocalized) {
6981
valueToReturn = valueToReturn[locale]
7082
}
83+
} else if (relatedDoc) {
84+
// Handle non-polymorphic `hasMany` relationships or fallback
85+
if (typeof relatedDoc.id !== 'undefined') {
86+
valueToReturn = relatedDoc.id
87+
} else {
88+
valueToReturn = relatedDoc
89+
}
90+
}
91+
92+
if (typeof valueToReturn === 'object' && valueToReturn !== null) {
93+
valueToReturn = JSON.stringify(valueToReturn)
7194
}
7295

7396
return valueToReturn
7497
}
7598

7699
const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, version }) => {
77-
let placeholder = ''
100+
const placeholder = `[${i18n.t('general:noValue')}]`
78101

79102
const { collections } = useConfig()
80103

81-
if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]`
104+
let versionToRender: string | undefined = placeholder
105+
let comparisonToRender: string | undefined = placeholder
82106

83-
let versionToRender = version
84-
let comparisonToRender = comparison
107+
if (version) {
108+
if ('hasMany' in field && field.hasMany && Array.isArray(version)) {
109+
versionToRender =
110+
version.map((val) => generateLabelFromValue(collections, field, locale, val)).join(', ') ||
111+
placeholder
112+
} else {
113+
versionToRender = generateLabelFromValue(collections, field, locale, version) || placeholder
114+
}
115+
}
85116

86-
if ('hasMany' in field && field.hasMany) {
87-
if (Array.isArray(version))
88-
versionToRender = version
89-
.map((val) => generateLabelFromValue(collections, field, locale, val))
90-
.join(', ')
91-
if (Array.isArray(comparison))
92-
comparisonToRender = comparison
93-
.map((val) => generateLabelFromValue(collections, field, locale, val))
94-
.join(', ')
95-
} else {
96-
versionToRender = generateLabelFromValue(collections, field, locale, version)
97-
comparisonToRender = generateLabelFromValue(collections, field, locale, comparison)
117+
if (comparison) {
118+
if ('hasMany' in field && field.hasMany && Array.isArray(comparison)) {
119+
comparisonToRender =
120+
comparison
121+
.map((val) => generateLabelFromValue(collections, field, locale, val))
122+
.join(', ') || placeholder
123+
} else {
124+
comparisonToRender =
125+
generateLabelFromValue(collections, field, locale, comparison) || placeholder
126+
}
98127
}
99128

100129
const label =
@@ -112,10 +141,8 @@ const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, versio
112141
</Label>
113142
<ReactDiffViewer
114143
hideLineNumbers
115-
newValue={typeof versionToRender !== 'undefined' ? String(versionToRender) : placeholder}
116-
oldValue={
117-
typeof comparisonToRender !== 'undefined' ? String(comparisonToRender) : placeholder
118-
}
144+
newValue={versionToRender}
145+
oldValue={comparisonToRender}
119146
showDiffOnly={false}
120147
splitView
121148
styles={diffStyles}

packages/next/src/views/Version/RenderFieldsToDiff/fields/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ export default {
1818
number: Text,
1919
point: Text,
2020
radio: Select,
21-
relationship: null,
21+
relationship: Relationship,
2222
richText: Text,
2323
row: Nested,
2424
select: Select,
2525
tabs: Tabs,
2626
text: Text,
2727
textarea: Text,
28-
upload: null,
28+
upload: Relationship,
2929
}

packages/payload/src/fields/hooks/afterRead/relationshipPopulationPromise.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,24 +126,25 @@ export const relationshipPopulationPromise = async ({
126126

127127
if (fieldSupportsMany(field) && field.hasMany) {
128128
if (
129+
field.localized &&
129130
locale === 'all' &&
130131
typeof siblingDoc[field.name] === 'object' &&
131132
siblingDoc[field.name] !== null
132133
) {
133-
Object.keys(siblingDoc[field.name]).forEach((key) => {
134-
if (Array.isArray(siblingDoc[field.name][key])) {
135-
siblingDoc[field.name][key].forEach((relatedDoc, index) => {
134+
Object.keys(siblingDoc[field.name]).forEach((localeKey) => {
135+
if (Array.isArray(siblingDoc[field.name][localeKey])) {
136+
siblingDoc[field.name][localeKey].forEach((relatedDoc, index) => {
136137
const rowPromise = async () => {
137138
await populate({
138139
currentDepth,
139-
data: siblingDoc[field.name][key][index],
140+
data: siblingDoc[field.name][localeKey][index],
140141
dataReference: resultingDoc,
141142
depth: populateDepth,
142143
draft,
143144
fallbackLocale,
144145
field,
145146
index,
146-
key,
147+
key: localeKey,
147148
locale,
148149
overrideAccess,
149150
req,
@@ -179,21 +180,22 @@ export const relationshipPopulationPromise = async ({
179180
})
180181
}
181182
} else if (
183+
field.localized &&
184+
locale === 'all' &&
182185
typeof siblingDoc[field.name] === 'object' &&
183-
siblingDoc[field.name] !== null &&
184-
locale === 'all'
186+
siblingDoc[field.name] !== null
185187
) {
186-
Object.keys(siblingDoc[field.name]).forEach((key) => {
188+
Object.keys(siblingDoc[field.name]).forEach((localeKey) => {
187189
const rowPromise = async () => {
188190
await populate({
189191
currentDepth,
190-
data: siblingDoc[field.name][key],
192+
data: siblingDoc[field.name][localeKey],
191193
dataReference: resultingDoc,
192194
depth: populateDepth,
193195
draft,
194196
fallbackLocale,
195197
field,
196-
key,
198+
key: localeKey,
197199
locale,
198200
overrideAccess,
199201
req,

test/fields-relationship/collectionSlugs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export const collection2Slug = 'collection-2'
1212
export const videoCollectionSlug = 'videos'
1313
export const podcastCollectionSlug = 'podcasts'
1414
export const mixedMediaCollectionSlug = 'mixed-media'
15+
export const versionedRelationshipFieldSlug = 'versioned-relationship-field'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
import { collection1Slug, versionedRelationshipFieldSlug } from '../../collectionSlugs.js'
4+
5+
export const VersionedRelationshipFieldCollection: CollectionConfig = {
6+
slug: versionedRelationshipFieldSlug,
7+
fields: [
8+
{
9+
name: 'title',
10+
type: 'text',
11+
required: true,
12+
},
13+
{
14+
name: 'relationshipField',
15+
type: 'relationship',
16+
relationTo: [collection1Slug],
17+
hasMany: true,
18+
},
19+
],
20+
versions: true,
21+
}

test/fields-relationship/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
slug,
2525
videoCollectionSlug,
2626
} from './collectionSlugs.js'
27+
import { VersionedRelationshipFieldCollection } from './collections/VersionedRelationshipField/index.js'
2728

2829
export interface FieldsRelationship {
2930
createdAt: Date
@@ -321,6 +322,9 @@ export default buildConfigWithDefaults({
321322
},
322323
],
323324
slug: collection1Slug,
325+
admin: {
326+
useAsTitle: 'name',
327+
},
324328
},
325329
{
326330
fields: [
@@ -376,7 +380,13 @@ export default buildConfigWithDefaults({
376380
},
377381
],
378382
},
383+
VersionedRelationshipFieldCollection,
379384
],
385+
localization: {
386+
locales: ['en'],
387+
defaultLocale: 'en',
388+
fallback: true,
389+
},
380390
onInit: async (payload) => {
381391
await payload.create({
382392
collection: 'users',

test/fields-relationship/int.spec.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import type { Payload } from 'payload'
2+
3+
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
4+
import type { Collection1 } from './payload-types.js'
5+
6+
import { devUser } from '../credentials.js'
7+
import { initPayloadInt } from '../helpers/initPayloadInt.js'
8+
import { collection1Slug, versionedRelationshipFieldSlug } from './collectionSlugs.js'
9+
import configPromise from './config.js'
10+
11+
let payload: Payload
12+
let restClient: NextRESTClient
13+
14+
const { email, password } = devUser
15+
16+
describe('Relationship Fields', () => {
17+
beforeAll(async () => {
18+
const initialized = await initPayloadInt(configPromise)
19+
;({ payload, restClient } = initialized)
20+
21+
await restClient.login({
22+
slug: 'users',
23+
credentials: {
24+
email,
25+
password,
26+
},
27+
})
28+
})
29+
30+
afterAll(async () => {
31+
if (typeof payload.db.destroy === 'function') {
32+
await payload.db.destroy()
33+
}
34+
})
35+
36+
describe('Versioned Relationship Field', () => {
37+
let version2ID: string
38+
const relatedDocName = 'Related Doc'
39+
beforeAll(async () => {
40+
const relatedDoc = await payload.create({
41+
collection: collection1Slug,
42+
data: {
43+
name: relatedDocName,
44+
},
45+
})
46+
47+
const version1 = await payload.create({
48+
collection: versionedRelationshipFieldSlug,
49+
data: {
50+
title: 'Version 1 Title',
51+
relationshipField: {
52+
value: relatedDoc.id,
53+
relationTo: collection1Slug,
54+
},
55+
},
56+
})
57+
58+
const version2 = await payload.update({
59+
collection: versionedRelationshipFieldSlug,
60+
id: version1.id,
61+
data: {
62+
title: 'Version 2 Title',
63+
},
64+
})
65+
66+
const versions = await payload.findVersions({
67+
collection: versionedRelationshipFieldSlug,
68+
where: {
69+
parent: {
70+
equals: version2.id,
71+
},
72+
},
73+
sort: '-updatedAt',
74+
limit: 1,
75+
})
76+
77+
version2ID = versions.docs[0].id
78+
})
79+
it('should return the correct versioned relationship field via REST', async () => {
80+
const version2Data = await restClient
81+
.GET(`/${versionedRelationshipFieldSlug}/versions/${version2ID}?locale=all`)
82+
.then((res) => res.json())
83+
84+
expect(version2Data.version.title).toEqual('Version 2 Title')
85+
expect(version2Data.version.relationshipField[0].value.name).toEqual(relatedDocName)
86+
})
87+
88+
it('should return the correct versioned relationship field via LocalAPI', async () => {
89+
const version2Data = await payload.findVersionByID({
90+
collection: versionedRelationshipFieldSlug,
91+
id: version2ID,
92+
locale: 'all',
93+
})
94+
95+
expect(version2Data.version.title).toEqual('Version 2 Title')
96+
expect((version2Data.version.relationshipField[0].value as Collection1).name).toEqual(
97+
relatedDocName,
98+
)
99+
})
100+
})
101+
})

0 commit comments

Comments
 (0)