Skip to content

Commit

Permalink
fix: copy to locale with localized arrays and blocks generate new IDs…
Browse files Browse the repository at this point in the history
… to prevent errors in postgres (#10292)

Fixes #10093
  • Loading branch information
r1tsuu authored Jan 3, 2025
1 parent d68a1ea commit c7b3204
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 7 deletions.
23 changes: 19 additions & 4 deletions packages/ui/src/utilities/copyDataFromLocale.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ObjectIdImport from 'bson-objectid'
import {
type CollectionSlug,
type Data,
Expand All @@ -7,6 +8,9 @@ import {
} from 'payload'
import { fieldAffectsData, tabHasName } from 'payload/shared'

const ObjectId = (ObjectIdImport.default ||
ObjectIdImport) as unknown as typeof ObjectIdImport.default

export type CopyDataFromLocaleArgs = {
collectionSlug?: CollectionSlug
docID?: number | string
Expand All @@ -33,10 +37,15 @@ function iterateFields(fields: Field[], fromLocaleData: Data, toLocaleData: Data
break
}

// if the field has a value but is not localized, loop over the data from target
if (!field.localized && field.name in toLocaleData) {
// if the field has a value - loop over the data from target
if (field.name in toLocaleData) {
toLocaleData[field.name].map((item: Data, index: number) => {
if (fromLocaleData[field.name]?.[index]) {
// Generate new IDs if the field is localized to prevent errors with relational DBs.
if (field.localized) {
toLocaleData[field.name][index].id = new ObjectId().toHexString()
}

iterateFields(field.fields, fromLocaleData[field.name][index], item)
}
})
Expand All @@ -55,18 +64,24 @@ function iterateFields(fields: Field[], fromLocaleData: Data, toLocaleData: Data
break
}

// if the field has a value but is not localized, loop over the data from target
if (!field.localized && field.name in toLocaleData) {
// if the field has a value - loop over the data from target
if (field.name in toLocaleData) {
toLocaleData[field.name].map((blockData: Data, index: number) => {
const blockFields = field.blocks.find(
({ slug }) => slug === blockData.blockType,
)?.fields

// Generate new IDs if the field is localized to prevent errors with relational DBs.
if (field.localized) {
toLocaleData[field.name][index].id = new ObjectId().toHexString()
}

if (blockFields?.length) {
iterateFields(blockFields, fromLocaleData[field.name][index], blockData)
}
})
}

break

case 'checkbox':
Expand Down
11 changes: 11 additions & 0 deletions test/localization/collections/NestedToArrayAndBlock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,16 @@ export const NestedToArrayAndBlock: CollectionConfig = {
},
],
},
{
name: 'topLevelArrayLocalized',
type: 'array',
localized: true,
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
}
2 changes: 1 addition & 1 deletion test/localization/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ describe('Localization', () => {
const nestedArrayURL = new AdminUrlUtil(serverURL, nestedToArrayAndBlockCollectionSlug)
await page.goto(nestedArrayURL.create)
await changeLocale(page, 'ar')
const addArrayRow = page.locator('.array-field__add-row')
const addArrayRow = page.locator('#field-topLevelArray .array-field__add-row')
await addArrayRow.click()

const arrayField = page.locator('#field-topLevelArray__0__localizedText')
Expand Down
117 changes: 115 additions & 2 deletions test/localization/int.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import type { Payload, User, Where } from 'payload'

import path from 'path'
import { type Payload, type Where } from 'payload'
import { createLocalReq } from 'payload'
import { fileURLToPath } from 'url'

import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import type { LocalizedPost, LocalizedSort, WithLocalizedRelationship } from './payload-types.js'
import type {
LocalizedPost,
LocalizedSort,
Nested,
WithLocalizedRelationship,
} from './payload-types.js'

import { devUser } from '../credentials.js'

// eslint-disable-next-line payload/no-relative-monorepo-imports
import { copyDataFromLocaleHandler } from '../../packages/ui/src/utilities/copyDataFromLocale.js'
import { idToString } from '../helpers/idToString.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
import { arrayCollectionSlug } from './collections/Array/index.js'
Expand Down Expand Up @@ -2451,6 +2462,108 @@ describe('Localization', () => {
).rejects.toBeTruthy()
})
})

describe('Copying To Locale', () => {
let user: User

beforeAll(async () => {
user = (
await payload.find({
collection: 'users',
where: {
email: {
equals: devUser.email,
},
},
})
).docs[0] as unknown as User

user['collection'] = 'users'
})

it('should copy to locale', async () => {
const doc = await payload.create({
collection: 'localized-posts',
data: {
title: 'Hello',
group: {
children: 'Children',
},
unique: 'unique-field',
localizedCheckbox: true,
},
})

const req = await createLocalReq({ user }, payload)

const res = (await copyDataFromLocaleHandler({
fromLocale: 'en',
req,
toLocale: 'es',
docID: doc.id,
collectionSlug: 'localized-posts',
})) as LocalizedPost

expect(res.title).toBe('Hello')
expect(res.group.children).toBe('Children')
expect(res.unique).toBe('unique-field')
expect(res.localizedCheckbox).toBe(true)
})

it('should copy localized nested to arrays', async () => {
const doc = await payload.create({
collection: 'nested',
locale: 'en',
data: {
topLevelArray: [
{
localizedText: 'some-localized-text',
notLocalizedText: 'some-not-localized-text',
},
],
},
})

const req = await createLocalReq({ user }, payload)

const res = (await copyDataFromLocaleHandler({
fromLocale: 'en',
req,
toLocale: 'es',
docID: doc.id,
collectionSlug: 'nested',
})) as Nested

expect(res.topLevelArray[0].localizedText).toBe('some-localized-text')
expect(res.topLevelArray[0].notLocalizedText).toBe('some-not-localized-text')
})

it('should copy localized arrays', async () => {
const doc = await payload.create({
collection: 'nested',
locale: 'en',
data: {
topLevelArrayLocalized: [
{
text: 'some-text',
},
],
},
})

const req = await createLocalReq({ user }, payload)

const res = (await copyDataFromLocaleHandler({
fromLocale: 'en',
req,
toLocale: 'es',
docID: doc.id,
collectionSlug: 'nested',
})) as Nested

expect(res.topLevelArrayLocalized[0].text).toBe('some-text')
})
})
})

describe('Localization with fallback false', () => {
Expand Down
12 changes: 12 additions & 0 deletions test/localization/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ export interface Nested {
id?: string | null;
}[]
| null;
topLevelArrayLocalized?:
| {
text?: string | null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
Expand Down Expand Up @@ -1051,6 +1057,12 @@ export interface NestedSelect<T extends boolean = true> {
notLocalizedText?: T;
id?: T;
};
topLevelArrayLocalized?:
| T
| {
text?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
}
Expand Down

0 comments on commit c7b3204

Please sign in to comment.