fix(db-postgres): ensure deletion of numbers and texts in upsertRow (#11787)
### What? This PR fixes an issue while using `text` & `number` fields with `hasMany: true` where the last entry would be unreachable, and thus undeletable, because the `transformForWrite` function did not track these rows for deletion. This causes values that should've been deleted to remain in the edit view form, as well as the db, after a submission. This PR also properly threads the placeholder value from `admin.placeholder` to `text` & `number` `hasMany: true` fields. ### Why? To remove rows from the db when a submission is made where these fields are empty arrays, and to properly show an appropriate placeholder when one is set in config. ### How? Adjusting `transformForWrite` and the `traverseFields` to keep track of rows for deletion. Fixes #11781 Before: [Editing---Post-dbpg-before--Payload.webm](https://github.com/user-attachments/assets/5ba1708a-2672-4b36-ac68-05212f3aa6cb) After: [Editing---Post--dbpg-hasmany-after-Payload.webm](https://github.com/user-attachments/assets/1292e998-83ff-49d0-aa86-6199be319937)
This commit is contained in:
@@ -98,6 +98,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
}: TraverseFieldsArgs): T => {
|
}: TraverseFieldsArgs): T => {
|
||||||
const sanitizedPath = path ? `${path}.` : path
|
const sanitizedPath = path ? `${path}.` : path
|
||||||
|
const localeCodes =
|
||||||
|
adapter.payload.config.localization && adapter.payload.config.localization.localeCodes
|
||||||
|
|
||||||
const formatted = fields.reduce((result, field) => {
|
const formatted = fields.reduce((result, field) => {
|
||||||
if (fieldIsVirtual(field)) {
|
if (fieldIsVirtual(field)) {
|
||||||
@@ -506,6 +508,10 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
if (field.type === 'text' && field?.hasMany) {
|
if (field.type === 'text' && field?.hasMany) {
|
||||||
const textPathMatch = texts[`${sanitizedPath}${field.name}`]
|
const textPathMatch = texts[`${sanitizedPath}${field.name}`]
|
||||||
if (!textPathMatch) {
|
if (!textPathMatch) {
|
||||||
|
result[field.name] =
|
||||||
|
isLocalized && localeCodes
|
||||||
|
? Object.fromEntries(localeCodes.map((locale) => [locale, []]))
|
||||||
|
: []
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,6 +551,10 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
if (field.type === 'number' && field.hasMany) {
|
if (field.type === 'number' && field.hasMany) {
|
||||||
const numberPathMatch = numbers[`${sanitizedPath}${field.name}`]
|
const numberPathMatch = numbers[`${sanitizedPath}${field.name}`]
|
||||||
if (!numberPathMatch) {
|
if (!numberPathMatch) {
|
||||||
|
result[field.name] =
|
||||||
|
isLocalized && localeCodes
|
||||||
|
? Object.fromEntries(localeCodes.map((locale) => [locale, []]))
|
||||||
|
: []
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,10 +616,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isLocalized && Array.isArray(table._locales)) {
|
if (isLocalized && Array.isArray(table._locales)) {
|
||||||
if (!table._locales.length && adapter.payload.config.localization) {
|
if (!table._locales.length && localeCodes) {
|
||||||
adapter.payload.config.localization.localeCodes.forEach((_locale) =>
|
localeCodes.forEach((_locale) => (table._locales as unknown[]).push({ _locale }))
|
||||||
(table._locales as unknown[]).push({ _locale }),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table._locales.forEach((localeRow) => {
|
table._locales.forEach((localeRow) => {
|
||||||
@@ -725,8 +733,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return result
|
|
||||||
}, dataRef)
|
}, dataRef)
|
||||||
|
|
||||||
if (Array.isArray(table._locales)) {
|
if (Array.isArray(table._locales)) {
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ import type { FlattenedArrayField } from 'payload'
|
|||||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||||
|
|
||||||
import type { DrizzleAdapter } from '../../types.js'
|
import type { DrizzleAdapter } from '../../types.js'
|
||||||
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types.js'
|
import type {
|
||||||
|
ArrayRowToInsert,
|
||||||
|
BlockRowToInsert,
|
||||||
|
NumberToDelete,
|
||||||
|
RelationshipToDelete,
|
||||||
|
TextToDelete,
|
||||||
|
} from './types.js'
|
||||||
|
|
||||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
|
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
|
||||||
import { traverseFields } from './traverseFields.js'
|
import { traverseFields } from './traverseFields.js'
|
||||||
@@ -20,6 +26,7 @@ type Args = {
|
|||||||
field: FlattenedArrayField
|
field: FlattenedArrayField
|
||||||
locale?: string
|
locale?: string
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
|
numbersToDelete: NumberToDelete[]
|
||||||
parentIsLocalized: boolean
|
parentIsLocalized: boolean
|
||||||
path: string
|
path: string
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
@@ -28,6 +35,7 @@ type Args = {
|
|||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
texts: Record<string, unknown>[]
|
texts: Record<string, unknown>[]
|
||||||
|
textsToDelete: TextToDelete[]
|
||||||
/**
|
/**
|
||||||
* Set to a locale code if this set of fields is traversed within a
|
* Set to a locale code if this set of fields is traversed within a
|
||||||
* localized array or block field
|
* localized array or block field
|
||||||
@@ -45,12 +53,14 @@ export const transformArray = ({
|
|||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized,
|
parentIsLocalized,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
}: Args) => {
|
}: Args) => {
|
||||||
const newRows: ArrayRowToInsert[] = []
|
const newRows: ArrayRowToInsert[] = []
|
||||||
@@ -104,6 +114,7 @@ export const transformArray = ({
|
|||||||
insideArrayOrBlock: true,
|
insideArrayOrBlock: true,
|
||||||
locales: newRow.locales,
|
locales: newRow.locales,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
parentTableName: arrayTableName,
|
parentTableName: arrayTableName,
|
||||||
path: `${path || ''}${field.name}.${i}.`,
|
path: `${path || ''}${field.name}.${i}.`,
|
||||||
@@ -112,6 +123,7 @@ export const transformArray = ({
|
|||||||
row: newRow.row,
|
row: newRow.row,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { fieldShouldBeLocalized } from 'payload/shared'
|
|||||||
import toSnakeCase from 'to-snake-case'
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { DrizzleAdapter } from '../../types.js'
|
import type { DrizzleAdapter } from '../../types.js'
|
||||||
import type { BlockRowToInsert, RelationshipToDelete } from './types.js'
|
import type {
|
||||||
|
BlockRowToInsert,
|
||||||
|
NumberToDelete,
|
||||||
|
RelationshipToDelete,
|
||||||
|
TextToDelete,
|
||||||
|
} from './types.js'
|
||||||
|
|
||||||
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
|
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
|
||||||
import { traverseFields } from './traverseFields.js'
|
import { traverseFields } from './traverseFields.js'
|
||||||
@@ -20,6 +25,7 @@ type Args = {
|
|||||||
field: FlattenedBlocksField
|
field: FlattenedBlocksField
|
||||||
locale?: string
|
locale?: string
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
|
numbersToDelete: NumberToDelete[]
|
||||||
parentIsLocalized: boolean
|
parentIsLocalized: boolean
|
||||||
path: string
|
path: string
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
@@ -28,6 +34,7 @@ type Args = {
|
|||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
texts: Record<string, unknown>[]
|
texts: Record<string, unknown>[]
|
||||||
|
textsToDelete: TextToDelete[]
|
||||||
/**
|
/**
|
||||||
* Set to a locale code if this set of fields is traversed within a
|
* Set to a locale code if this set of fields is traversed within a
|
||||||
* localized array or block field
|
* localized array or block field
|
||||||
@@ -43,12 +50,14 @@ export const transformBlocks = ({
|
|||||||
field,
|
field,
|
||||||
locale,
|
locale,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized,
|
parentIsLocalized,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
}: Args) => {
|
}: Args) => {
|
||||||
data.forEach((blockRow, i) => {
|
data.forEach((blockRow, i) => {
|
||||||
@@ -117,6 +126,7 @@ export const transformBlocks = ({
|
|||||||
insideArrayOrBlock: true,
|
insideArrayOrBlock: true,
|
||||||
locales: newRow.locales,
|
locales: newRow.locales,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
parentTableName: blockTableName,
|
parentTableName: blockTableName,
|
||||||
path: `${path || ''}${field.name}.${i}.`,
|
path: `${path || ''}${field.name}.${i}.`,
|
||||||
@@ -125,6 +135,7 @@ export const transformBlocks = ({
|
|||||||
row: newRow.row,
|
row: newRow.row,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ export const transformForWrite = ({
|
|||||||
blocksToDelete: new Set(),
|
blocksToDelete: new Set(),
|
||||||
locales: {},
|
locales: {},
|
||||||
numbers: [],
|
numbers: [],
|
||||||
|
numbersToDelete: [],
|
||||||
relationships: [],
|
relationships: [],
|
||||||
relationshipsToDelete: [],
|
relationshipsToDelete: [],
|
||||||
row: {},
|
row: {},
|
||||||
selects: {},
|
selects: {},
|
||||||
texts: [],
|
texts: [],
|
||||||
|
textsToDelete: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is responsible for building up the
|
// This function is responsible for building up the
|
||||||
@@ -50,6 +52,7 @@ export const transformForWrite = ({
|
|||||||
fields,
|
fields,
|
||||||
locales: rowToInsert.locales,
|
locales: rowToInsert.locales,
|
||||||
numbers: rowToInsert.numbers,
|
numbers: rowToInsert.numbers,
|
||||||
|
numbersToDelete: rowToInsert.numbersToDelete,
|
||||||
parentIsLocalized,
|
parentIsLocalized,
|
||||||
parentTableName: tableName,
|
parentTableName: tableName,
|
||||||
path,
|
path,
|
||||||
@@ -58,6 +61,7 @@ export const transformForWrite = ({
|
|||||||
row: rowToInsert.row,
|
row: rowToInsert.row,
|
||||||
selects: rowToInsert.selects,
|
selects: rowToInsert.selects,
|
||||||
texts: rowToInsert.texts,
|
texts: rowToInsert.texts,
|
||||||
|
textsToDelete: rowToInsert.textsToDelete,
|
||||||
})
|
})
|
||||||
|
|
||||||
return rowToInsert
|
return rowToInsert
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
|||||||
import toSnakeCase from 'to-snake-case'
|
import toSnakeCase from 'to-snake-case'
|
||||||
|
|
||||||
import type { DrizzleAdapter } from '../../types.js'
|
import type { DrizzleAdapter } from '../../types.js'
|
||||||
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types.js'
|
import type {
|
||||||
|
ArrayRowToInsert,
|
||||||
|
BlockRowToInsert,
|
||||||
|
NumberToDelete,
|
||||||
|
RelationshipToDelete,
|
||||||
|
TextToDelete,
|
||||||
|
} from './types.js'
|
||||||
|
|
||||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
|
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
|
||||||
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
|
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
|
||||||
@@ -51,6 +57,7 @@ type Args = {
|
|||||||
[locale: string]: Record<string, unknown>
|
[locale: string]: Record<string, unknown>
|
||||||
}
|
}
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
|
numbersToDelete: NumberToDelete[]
|
||||||
parentIsLocalized: boolean
|
parentIsLocalized: boolean
|
||||||
/**
|
/**
|
||||||
* This is the name of the parent table
|
* This is the name of the parent table
|
||||||
@@ -64,6 +71,7 @@ type Args = {
|
|||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
texts: Record<string, unknown>[]
|
texts: Record<string, unknown>[]
|
||||||
|
textsToDelete: TextToDelete[]
|
||||||
/**
|
/**
|
||||||
* Set to a locale code if this set of fields is traversed within a
|
* Set to a locale code if this set of fields is traversed within a
|
||||||
* localized array or block field
|
* localized array or block field
|
||||||
@@ -86,6 +94,7 @@ export const traverseFields = ({
|
|||||||
insideArrayOrBlock = false,
|
insideArrayOrBlock = false,
|
||||||
locales,
|
locales,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized,
|
parentIsLocalized,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path,
|
path,
|
||||||
@@ -94,6 +103,7 @@ export const traverseFields = ({
|
|||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
}: Args) => {
|
}: Args) => {
|
||||||
if (row._uuid) {
|
if (row._uuid) {
|
||||||
@@ -136,12 +146,14 @@ export const traverseFields = ({
|
|||||||
field,
|
field,
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale: localeKey,
|
withinArrayOrBlockLocale: localeKey,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -159,12 +171,14 @@ export const traverseFields = ({
|
|||||||
data: data[field.name],
|
data: data[field.name],
|
||||||
field,
|
field,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -202,12 +216,14 @@ export const traverseFields = ({
|
|||||||
field,
|
field,
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale: localeKey,
|
withinArrayOrBlockLocale: localeKey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -222,12 +238,14 @@ export const traverseFields = ({
|
|||||||
data: fieldData,
|
data: fieldData,
|
||||||
field,
|
field,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
path,
|
path,
|
||||||
relationships,
|
relationships,
|
||||||
relationshipsToDelete,
|
relationshipsToDelete,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -257,6 +275,7 @@ export const traverseFields = ({
|
|||||||
insideArrayOrBlock,
|
insideArrayOrBlock,
|
||||||
locales,
|
locales,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${field.name}.`,
|
path: `${path || ''}${field.name}.`,
|
||||||
@@ -265,6 +284,7 @@ export const traverseFields = ({
|
|||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale: localeKey,
|
withinArrayOrBlockLocale: localeKey,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -287,6 +307,7 @@ export const traverseFields = ({
|
|||||||
insideArrayOrBlock,
|
insideArrayOrBlock,
|
||||||
locales,
|
locales,
|
||||||
numbers,
|
numbers,
|
||||||
|
numbersToDelete,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
parentTableName,
|
parentTableName,
|
||||||
path: `${path || ''}${field.name}.`,
|
path: `${path || ''}${field.name}.`,
|
||||||
@@ -295,6 +316,7 @@ export const traverseFields = ({
|
|||||||
row,
|
row,
|
||||||
selects,
|
selects,
|
||||||
texts,
|
texts,
|
||||||
|
textsToDelete,
|
||||||
withinArrayOrBlockLocale,
|
withinArrayOrBlockLocale,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -380,6 +402,11 @@ export const traverseFields = ({
|
|||||||
if (typeof fieldData === 'object') {
|
if (typeof fieldData === 'object') {
|
||||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||||
if (Array.isArray(localeData)) {
|
if (Array.isArray(localeData)) {
|
||||||
|
if (!localeData.length) {
|
||||||
|
textsToDelete.push({ locale: localeKey, path: textPath })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
transformTexts({
|
transformTexts({
|
||||||
baseRow: {
|
baseRow: {
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
@@ -392,6 +419,11 @@ export const traverseFields = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(fieldData)) {
|
} else if (Array.isArray(fieldData)) {
|
||||||
|
if (!fieldData.length) {
|
||||||
|
textsToDelete.push({ locale: withinArrayOrBlockLocale, path: textPath })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
transformTexts({
|
transformTexts({
|
||||||
baseRow: {
|
baseRow: {
|
||||||
locale: withinArrayOrBlockLocale,
|
locale: withinArrayOrBlockLocale,
|
||||||
@@ -412,6 +444,11 @@ export const traverseFields = ({
|
|||||||
if (typeof fieldData === 'object') {
|
if (typeof fieldData === 'object') {
|
||||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||||
if (Array.isArray(localeData)) {
|
if (Array.isArray(localeData)) {
|
||||||
|
if (!localeData.length) {
|
||||||
|
numbersToDelete.push({ locale: localeKey, path: numberPath })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
transformNumbers({
|
transformNumbers({
|
||||||
baseRow: {
|
baseRow: {
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
@@ -424,6 +461,11 @@ export const traverseFields = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(fieldData)) {
|
} else if (Array.isArray(fieldData)) {
|
||||||
|
if (!fieldData.length) {
|
||||||
|
numbersToDelete.push({ locale: withinArrayOrBlockLocale, path: numberPath })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
transformNumbers({
|
transformNumbers({
|
||||||
baseRow: {
|
baseRow: {
|
||||||
locale: withinArrayOrBlockLocale,
|
locale: withinArrayOrBlockLocale,
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ export type RelationshipToDelete = {
|
|||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TextToDelete = {
|
||||||
|
locale?: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NumberToDelete = {
|
||||||
|
locale?: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
export type RowToInsert = {
|
export type RowToInsert = {
|
||||||
arrays: {
|
arrays: {
|
||||||
[tableName: string]: ArrayRowToInsert[]
|
[tableName: string]: ArrayRowToInsert[]
|
||||||
@@ -35,6 +45,7 @@ export type RowToInsert = {
|
|||||||
[locale: string]: Record<string, unknown>
|
[locale: string]: Record<string, unknown>
|
||||||
}
|
}
|
||||||
numbers: Record<string, unknown>[]
|
numbers: Record<string, unknown>[]
|
||||||
|
numbersToDelete: NumberToDelete[]
|
||||||
relationships: Record<string, unknown>[]
|
relationships: Record<string, unknown>[]
|
||||||
relationshipsToDelete: RelationshipToDelete[]
|
relationshipsToDelete: RelationshipToDelete[]
|
||||||
row: Record<string, unknown>
|
row: Record<string, unknown>
|
||||||
@@ -42,4 +53,5 @@ export type RowToInsert = {
|
|||||||
[tableName: string]: Record<string, unknown>[]
|
[tableName: string]: Record<string, unknown>[]
|
||||||
}
|
}
|
||||||
texts: Record<string, unknown>[]
|
texts: Record<string, unknown>[]
|
||||||
|
textsToDelete: TextToDelete[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
|||||||
parentColumnName: 'parent',
|
parentColumnName: 'parent',
|
||||||
parentID: insertedRow.id,
|
parentID: insertedRow.id,
|
||||||
pathColumnName: 'path',
|
pathColumnName: 'path',
|
||||||
rows: textsToInsert,
|
rows: [...textsToInsert, ...rowToInsert.textsToDelete],
|
||||||
tableName: textsTableName,
|
tableName: textsTableName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
|||||||
parentColumnName: 'parent',
|
parentColumnName: 'parent',
|
||||||
parentID: insertedRow.id,
|
parentID: insertedRow.id,
|
||||||
pathColumnName: 'path',
|
pathColumnName: 'path',
|
||||||
rows: numbersToInsert,
|
rows: [...numbersToInsert, ...rowToInsert.numbersToDelete],
|
||||||
tableName: numbersTableName,
|
tableName: numbersTableName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
|||||||
admin: {
|
admin: {
|
||||||
className,
|
className,
|
||||||
description,
|
description,
|
||||||
placeholder,
|
placeholder: placeholderFromProps,
|
||||||
step = 1,
|
step = 1,
|
||||||
} = {} as NumberFieldClientProps['field']['admin'],
|
} = {} as NumberFieldClientProps['field']['admin'],
|
||||||
hasMany = false,
|
hasMany = false,
|
||||||
@@ -126,6 +126,8 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
|||||||
|
|
||||||
const styles = useMemo(() => mergeFieldStyles(field), [field])
|
const styles = useMemo(() => mergeFieldStyles(field), [field])
|
||||||
|
|
||||||
|
const placeholder = getTranslation(placeholderFromProps, i18n)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
@@ -174,7 +176,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
|||||||
// numberOnly
|
// numberOnly
|
||||||
onChange={handleHasManyChange}
|
onChange={handleHasManyChange}
|
||||||
options={[]}
|
options={[]}
|
||||||
placeholder={t('general:enterAValue')}
|
placeholder={placeholder}
|
||||||
showError={showError}
|
showError={showError}
|
||||||
value={valueToRender as Option[]}
|
value={valueToRender as Option[]}
|
||||||
/>
|
/>
|
||||||
@@ -191,7 +193,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
e.target.blur()
|
e.target.blur()
|
||||||
}}
|
}}
|
||||||
placeholder={getTranslation(placeholder, i18n)}
|
placeholder={placeholder}
|
||||||
step={step}
|
step={step}
|
||||||
type="number"
|
type="number"
|
||||||
value={typeof value === 'number' ? value : ''}
|
value={typeof value === 'number' ? value : ''}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
onChange,
|
onChange,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
path,
|
path,
|
||||||
placeholder,
|
placeholder: placeholderFromProps,
|
||||||
readOnly,
|
readOnly,
|
||||||
required,
|
required,
|
||||||
rtl,
|
rtl,
|
||||||
@@ -91,6 +91,8 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const placeholder = getTranslation(placeholderFromProps, i18n)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
@@ -143,7 +145,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={[]}
|
options={[]}
|
||||||
placeholder={t('general:enterAValue')}
|
placeholder={placeholder}
|
||||||
showError={showError}
|
showError={showError}
|
||||||
value={valueToRender}
|
value={valueToRender}
|
||||||
/>
|
/>
|
||||||
@@ -155,7 +157,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
name={path}
|
name={path}
|
||||||
onChange={onChange as (e: ChangeEvent<HTMLInputElement>) => void}
|
onChange={onChange as (e: ChangeEvent<HTMLInputElement>) => void}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
placeholder={getTranslation(placeholder, i18n)}
|
placeholder={placeholder}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
value={value || ''}
|
value={value || ''}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
checkboxFieldsSlug,
|
checkboxFieldsSlug,
|
||||||
collapsibleFieldsSlug,
|
collapsibleFieldsSlug,
|
||||||
groupFieldsSlug,
|
groupFieldsSlug,
|
||||||
|
numberFieldsSlug,
|
||||||
relationshipFieldsSlug,
|
relationshipFieldsSlug,
|
||||||
tabsFieldsSlug,
|
tabsFieldsSlug,
|
||||||
textFieldsSlug,
|
textFieldsSlug,
|
||||||
@@ -401,6 +402,31 @@ describe('Fields', () => {
|
|||||||
|
|
||||||
expect(resInSecond.totalDocs).toBe(1)
|
expect(resInSecond.totalDocs).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should delete rows when updating hasMany with empty array', async () => {
|
||||||
|
const { id: createdDocId } = await payload.create({
|
||||||
|
collection: textFieldsSlug,
|
||||||
|
data: {
|
||||||
|
text: 'hasMany deletion test',
|
||||||
|
hasMany: ['one', 'two', 'three'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.update({
|
||||||
|
collection: textFieldsSlug,
|
||||||
|
id: createdDocId,
|
||||||
|
data: {
|
||||||
|
hasMany: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const resultingDoc = await payload.findByID({
|
||||||
|
collection: textFieldsSlug,
|
||||||
|
id: createdDocId,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(resultingDoc.hasMany).toHaveLength(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('relationship', () => {
|
describe('relationship', () => {
|
||||||
@@ -1042,6 +1068,30 @@ describe('Fields', () => {
|
|||||||
|
|
||||||
expect(numbersNotExists.docs).toHaveLength(1)
|
expect(numbersNotExists.docs).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should delete rows when updating hasMany with empty array', async () => {
|
||||||
|
const { id: createdDocId } = await payload.create({
|
||||||
|
collection: numberFieldsSlug,
|
||||||
|
data: {
|
||||||
|
localizedHasMany: [1, 2, 3],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.update({
|
||||||
|
collection: numberFieldsSlug,
|
||||||
|
id: createdDocId,
|
||||||
|
data: {
|
||||||
|
localizedHasMany: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const resultingDoc = await payload.findByID({
|
||||||
|
collection: numberFieldsSlug,
|
||||||
|
id: createdDocId,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(resultingDoc.localizedHasMany).toHaveLength(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should query hasMany within an array', async () => {
|
it('should query hasMany within an array', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user