fix(next, payload): enable relationship & upload version tracking when localization enabled (#7508)

This commit is contained in:
Patrik
2024-08-06 12:28:06 -04:00
committed by GitHub
parent e8bed7b315
commit 62744e79ac
9 changed files with 237 additions and 43 deletions

7
.vscode/launch.json vendored
View File

@@ -56,6 +56,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js fields-relationship",
"cwd": "${workspaceFolder}",
"name": "Run Dev Fields-Relationship",
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js login-with-username",
"cwd": "${workspaceFolder}",

View File

@@ -26,7 +26,13 @@ const generateLabelFromValue = (
locale: string,
value: { relationTo: string; value: RelationshipValue } | RelationshipValue,
): string => {
let relation: string
if (Array.isArray(value)) {
return value
.map((v) => generateLabelFromValue(collections, field, locale, v))
.filter(Boolean) // Filters out any undefined or empty values
.join(', ')
}
let relatedDoc: RelationshipValue
let valueToReturn = '' as any
@@ -37,17 +43,20 @@ const generateLabelFromValue = (
return String(value)
}
if (Array.isArray(relationTo)) {
if (typeof value === 'object') {
relation = value.relationTo
if (typeof value === 'object' && 'relationTo' in value) {
relatedDoc = value.value
}
} else {
relation = relationTo
// Non-polymorphic relationship
relatedDoc = value
}
const relatedCollection = collections.find((c) => c.slug === relation)
const relatedCollection = relationTo
? collections.find(
(c) =>
c.slug ===
(typeof value === 'object' && 'relationTo' in value ? value.relationTo : relationTo),
)
: null
if (relatedCollection) {
const useAsTitle = relatedCollection?.admin?.useAsTitle
@@ -56,45 +65,65 @@ const generateLabelFromValue = (
)
let titleFieldIsLocalized = false
if (useAsTitleField && fieldAffectsData(useAsTitleField))
if (useAsTitleField && fieldAffectsData(useAsTitleField)) {
titleFieldIsLocalized = useAsTitleField.localized
}
if (typeof relatedDoc?.[useAsTitle] !== 'undefined') {
valueToReturn = relatedDoc[useAsTitle]
} else if (typeof relatedDoc?.id !== 'undefined') {
valueToReturn = relatedDoc.id
} else {
valueToReturn = relatedDoc
}
if (typeof valueToReturn === 'object' && titleFieldIsLocalized) {
valueToReturn = valueToReturn[locale]
}
} else if (relatedDoc) {
// Handle non-polymorphic `hasMany` relationships or fallback
if (typeof relatedDoc.id !== 'undefined') {
valueToReturn = relatedDoc.id
} else {
valueToReturn = relatedDoc
}
}
if (typeof valueToReturn === 'object' && valueToReturn !== null) {
valueToReturn = JSON.stringify(valueToReturn)
}
return valueToReturn
}
const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, version }) => {
let placeholder = ''
const placeholder = `[${i18n.t('general:noValue')}]`
const { collections } = useConfig()
if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]`
let versionToRender: string | undefined = placeholder
let comparisonToRender: string | undefined = placeholder
let versionToRender = version
let comparisonToRender = comparison
if ('hasMany' in field && field.hasMany) {
if (Array.isArray(version))
versionToRender = version
.map((val) => generateLabelFromValue(collections, field, locale, val))
.join(', ')
if (Array.isArray(comparison))
comparisonToRender = comparison
.map((val) => generateLabelFromValue(collections, field, locale, val))
.join(', ')
if (version) {
if ('hasMany' in field && field.hasMany && Array.isArray(version)) {
versionToRender =
version.map((val) => generateLabelFromValue(collections, field, locale, val)).join(', ') ||
placeholder
} else {
versionToRender = generateLabelFromValue(collections, field, locale, version)
comparisonToRender = generateLabelFromValue(collections, field, locale, comparison)
versionToRender = generateLabelFromValue(collections, field, locale, version) || placeholder
}
}
if (comparison) {
if ('hasMany' in field && field.hasMany && Array.isArray(comparison)) {
comparisonToRender =
comparison
.map((val) => generateLabelFromValue(collections, field, locale, val))
.join(', ') || placeholder
} else {
comparisonToRender =
generateLabelFromValue(collections, field, locale, comparison) || placeholder
}
}
const label =
@@ -112,10 +141,8 @@ const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, versio
</Label>
<ReactDiffViewer
hideLineNumbers
newValue={typeof versionToRender !== 'undefined' ? String(versionToRender) : placeholder}
oldValue={
typeof comparisonToRender !== 'undefined' ? String(comparisonToRender) : placeholder
}
newValue={versionToRender}
oldValue={comparisonToRender}
showDiffOnly={false}
splitView
styles={diffStyles}

View File

@@ -18,12 +18,12 @@ export default {
number: Text,
point: Text,
radio: Select,
relationship: null,
relationship: Relationship,
richText: Text,
row: Nested,
select: Select,
tabs: Tabs,
text: Text,
textarea: Text,
upload: null,
upload: Relationship,
}

View File

@@ -126,24 +126,25 @@ export const relationshipPopulationPromise = async ({
if (fieldSupportsMany(field) && field.hasMany) {
if (
field.localized &&
locale === 'all' &&
typeof siblingDoc[field.name] === 'object' &&
siblingDoc[field.name] !== null
) {
Object.keys(siblingDoc[field.name]).forEach((key) => {
if (Array.isArray(siblingDoc[field.name][key])) {
siblingDoc[field.name][key].forEach((relatedDoc, index) => {
Object.keys(siblingDoc[field.name]).forEach((localeKey) => {
if (Array.isArray(siblingDoc[field.name][localeKey])) {
siblingDoc[field.name][localeKey].forEach((relatedDoc, index) => {
const rowPromise = async () => {
await populate({
currentDepth,
data: siblingDoc[field.name][key][index],
data: siblingDoc[field.name][localeKey][index],
dataReference: resultingDoc,
depth: populateDepth,
draft,
fallbackLocale,
field,
index,
key,
key: localeKey,
locale,
overrideAccess,
req,
@@ -179,21 +180,22 @@ export const relationshipPopulationPromise = async ({
})
}
} else if (
field.localized &&
locale === 'all' &&
typeof siblingDoc[field.name] === 'object' &&
siblingDoc[field.name] !== null &&
locale === 'all'
siblingDoc[field.name] !== null
) {
Object.keys(siblingDoc[field.name]).forEach((key) => {
Object.keys(siblingDoc[field.name]).forEach((localeKey) => {
const rowPromise = async () => {
await populate({
currentDepth,
data: siblingDoc[field.name][key],
data: siblingDoc[field.name][localeKey],
dataReference: resultingDoc,
depth: populateDepth,
draft,
fallbackLocale,
field,
key,
key: localeKey,
locale,
overrideAccess,
req,

View File

@@ -12,3 +12,4 @@ export const collection2Slug = 'collection-2'
export const videoCollectionSlug = 'videos'
export const podcastCollectionSlug = 'podcasts'
export const mixedMediaCollectionSlug = 'mixed-media'
export const versionedRelationshipFieldSlug = 'versioned-relationship-field'

View File

@@ -0,0 +1,21 @@
import type { CollectionConfig } from 'payload'
import { collection1Slug, versionedRelationshipFieldSlug } from '../../collectionSlugs.js'
export const VersionedRelationshipFieldCollection: CollectionConfig = {
slug: versionedRelationshipFieldSlug,
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'relationshipField',
type: 'relationship',
relationTo: [collection1Slug],
hasMany: true,
},
],
versions: true,
}

View File

@@ -24,6 +24,7 @@ import {
slug,
videoCollectionSlug,
} from './collectionSlugs.js'
import { VersionedRelationshipFieldCollection } from './collections/VersionedRelationshipField/index.js'
export interface FieldsRelationship {
createdAt: Date
@@ -321,6 +322,9 @@ export default buildConfigWithDefaults({
},
],
slug: collection1Slug,
admin: {
useAsTitle: 'name',
},
},
{
fields: [
@@ -376,7 +380,13 @@ export default buildConfigWithDefaults({
},
],
},
VersionedRelationshipFieldCollection,
],
localization: {
locales: ['en'],
defaultLocale: 'en',
fallback: true,
},
onInit: async (payload) => {
await payload.create({
collection: 'users',

View File

@@ -0,0 +1,101 @@
import type { Payload } from 'payload'
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import type { Collection1 } from './payload-types.js'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { collection1Slug, versionedRelationshipFieldSlug } from './collectionSlugs.js'
import configPromise from './config.js'
let payload: Payload
let restClient: NextRESTClient
const { email, password } = devUser
describe('Relationship Fields', () => {
beforeAll(async () => {
const initialized = await initPayloadInt(configPromise)
;({ payload, restClient } = initialized)
await restClient.login({
slug: 'users',
credentials: {
email,
password,
},
})
})
afterAll(async () => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy()
}
})
describe('Versioned Relationship Field', () => {
let version2ID: string
const relatedDocName = 'Related Doc'
beforeAll(async () => {
const relatedDoc = await payload.create({
collection: collection1Slug,
data: {
name: relatedDocName,
},
})
const version1 = await payload.create({
collection: versionedRelationshipFieldSlug,
data: {
title: 'Version 1 Title',
relationshipField: {
value: relatedDoc.id,
relationTo: collection1Slug,
},
},
})
const version2 = await payload.update({
collection: versionedRelationshipFieldSlug,
id: version1.id,
data: {
title: 'Version 2 Title',
},
})
const versions = await payload.findVersions({
collection: versionedRelationshipFieldSlug,
where: {
parent: {
equals: version2.id,
},
},
sort: '-updatedAt',
limit: 1,
})
version2ID = versions.docs[0].id
})
it('should return the correct versioned relationship field via REST', async () => {
const version2Data = await restClient
.GET(`/${versionedRelationshipFieldSlug}/versions/${version2ID}?locale=all`)
.then((res) => res.json())
expect(version2Data.version.title).toEqual('Version 2 Title')
expect(version2Data.version.relationshipField[0].value.name).toEqual(relatedDocName)
})
it('should return the correct versioned relationship field via LocalAPI', async () => {
const version2Data = await payload.findVersionByID({
collection: versionedRelationshipFieldSlug,
id: version2ID,
locale: 'all',
})
expect(version2Data.version.title).toEqual('Version 2 Title')
expect((version2Data.version.relationshipField[0].value as Collection1).name).toEqual(
relatedDocName,
)
})
})
})

View File

@@ -24,12 +24,16 @@ export interface Config {
videos: Video;
podcasts: Podcast;
'mixed-media': MixedMedia;
'versioned-relationship-field': VersionedRelationshipField;
users: User;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
db: {
defaultIDType: string;
};
globals: {};
locale: null;
locale: 'en';
user: User & {
collection: 'users';
};
@@ -37,15 +41,20 @@ export interface Config {
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
password: string;
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -252,6 +261,22 @@ export interface MixedMedia {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "versioned-relationship-field".
*/
export interface VersionedRelationshipField {
id: string;
title: string;
relationshipField?:
| {
relationTo: 'collection-1';
value: string | Collection1;
}[]
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".