Compare commits

..

11 Commits

Author SHA1 Message Date
Elliot DeNolf
6ea6172afa chore(release): db-postgres/0.5.2 [skip ci] 2024-02-09 09:06:15 -05:00
Elliot DeNolf
486774796d chore(release): db-mongodb/1.4.1 [skip ci] 2024-02-09 09:06:06 -05:00
Elliot DeNolf
1cd1c38764 chore(release): payload/2.10.1 [skip ci] 2024-02-09 09:04:42 -05:00
Elliot DeNolf
f6d7da7510 fix: clearable cells handle null values (#5038) 2024-02-09 08:59:38 -05:00
Elliot DeNolf
cdc4cb971b fix(db-mongodb): handle null values with exists (#5037) 2024-02-09 08:58:10 -05:00
Elliot DeNolf
e0191b54e1 chore(release): richtext-lexical/0.6.1 [skip ci] 2024-02-08 11:49:02 -05:00
Alessio Gravili
2315781f18 fix(richtext-lexical): make editor reactive to initialValue changes (#5010) 2024-02-08 15:30:21 +01:00
Elliot DeNolf
a0a58e7fd2 fix: query relationships by explicit id field (#5022) 2024-02-07 14:18:13 -05:00
Jessica Chowdhury
e1813fb884 fix: ensures docs with the same id are shown in relationship field select (#4859) 2024-02-07 14:04:03 -05:00
Elliot DeNolf
da184d40ec fix(db-postgres): handle nested docs with drafts (#5012) 2024-02-06 21:27:33 -05:00
Elliot DeNolf
ca8675f89d chore(release): plugin-seo/2.2.1 [skip ci] 2024-02-06 15:41:58 -05:00
15 changed files with 266 additions and 26 deletions

View File

@@ -1,3 +1,15 @@
## [2.10.1](https://github.com/payloadcms/payload/compare/v2.10.0...v2.10.1) (2024-02-09)
### Bug Fixes
* clearable cells handle null values ([#5038](https://github.com/payloadcms/payload/issues/5038)) ([f6d7da7](https://github.com/payloadcms/payload/commit/f6d7da751039df25066b51bb91d6453e1a4efd82))
* **db-mongodb:** handle null values with exists ([#5037](https://github.com/payloadcms/payload/issues/5037)) ([cdc4cb9](https://github.com/payloadcms/payload/commit/cdc4cb971b9180ba2ed09741f5af1a3c18292828))
* **db-postgres:** handle nested docs with drafts ([#5012](https://github.com/payloadcms/payload/issues/5012)) ([da184d4](https://github.com/payloadcms/payload/commit/da184d40ece74bffb224002eb5df8f6987d65043))
* ensures docs with the same id are shown in relationship field select ([#4859](https://github.com/payloadcms/payload/issues/4859)) ([e1813fb](https://github.com/payloadcms/payload/commit/e1813fb884e0dc84203fcbab87527a99a4d3a5d7))
* query relationships by explicit id field ([#5022](https://github.com/payloadcms/payload/issues/5022)) ([a0a58e7](https://github.com/payloadcms/payload/commit/a0a58e7fd20dff54d210c968f4d5defd67441bdd))
* **richtext-lexical:** make editor reactive to initialValue changes ([#5010](https://github.com/payloadcms/payload/issues/5010)) ([2315781](https://github.com/payloadcms/payload/commit/2315781f1891ddde4b4c5f2f0cfa1c17af85b7a9))
## [2.10.0](https://github.com/payloadcms/payload/compare/v2.9.0...v2.10.0) (2024-02-06)

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "1.4.0",
"version": "1.4.1",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -157,6 +157,23 @@ export const sanitizeQueryValue = ({
if (operator === 'exists') {
formattedValue = formattedValue === 'true' || formattedValue === true
// Clearable fields
if (['relationship', 'select', 'upload'].includes(field.type)) {
if (formattedValue) {
return {
rawQuery: {
$and: [{ [path]: { $exists: true } }, { [path]: { $ne: null } }],
},
}
} else {
return {
rawQuery: {
$or: [{ [path]: { $exists: false } }, { [path]: { $eq: null } }],
},
}
}
}
}
return { operator: formattedOperator, val: formattedValue }

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.5.1",
"version": "0.5.2",
"description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -44,6 +44,10 @@ type Args = {
rootTableName?: string
selectFields: Record<string, GenericColumn>
tableName: string
/**
* If creating a new table name for arrays and blocks, this suffix should be appended to the table name
*/
tableNameSuffix?: string
}
/**
* Transforms path to table and column name
@@ -65,6 +69,7 @@ export const getTableColumnFromPath = ({
rootTableName: incomingRootTableName,
selectFields,
tableName,
tableNameSuffix = '',
}: Args): TableColumn => {
const fieldPath = incomingSegments[0]
let locale = incomingLocale
@@ -125,6 +130,7 @@ export const getTableColumnFromPath = ({
rootTableName,
selectFields,
tableName: newTableName,
tableNameSuffix,
})
}
case 'tab': {
@@ -144,6 +150,7 @@ export const getTableColumnFromPath = ({
rootTableName,
selectFields,
tableName: newTableName,
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
})
}
return getTableColumnFromPath({
@@ -161,6 +168,7 @@ export const getTableColumnFromPath = ({
rootTableName,
selectFields,
tableName: newTableName,
tableNameSuffix,
})
}
@@ -195,11 +203,12 @@ export const getTableColumnFromPath = ({
rootTableName,
selectFields,
tableName: newTableName,
tableNameSuffix: `${tableNameSuffix}${toSnakeCase(field.name)}_`,
})
}
case 'array': {
newTableName = `${tableName}_${toSnakeCase(field.name)}`
newTableName = `${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
@@ -340,7 +349,7 @@ export const getTableColumnFromPath = ({
table: newAliasTable,
})
if (newCollectionPath === '') {
if (newCollectionPath === '' || newCollectionPath === 'id') {
return {
columnName: `${field.relationTo}ID`,
constraints,

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "2.10.0",
"version": "2.10.1",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./dist/index.js",

View File

@@ -9,7 +9,7 @@ const reduceToIDs = (options) =>
return [...ids, ...reduceToIDs(option.options)]
}
return [...ids, option.value]
return [...ids, { id: option.value, relationTo: option.relationTo }]
}, [])
const sortOptions = (options: Option[]): Option[] =>
@@ -63,10 +63,12 @@ const optionsReducer = (state: OptionGroup[], action: Action): OptionGroup[] =>
const optionsToAddTo = newOptions.find(
(optionGroup) => optionGroup.label === collection.labels.plural,
)
const newSubOptions = docs.reduce((docSubOptions, doc) => {
if (loadedIDs.indexOf(doc.id) === -1) {
loadedIDs.push(doc.id)
if (
loadedIDs.filter((item) => item.id === doc.id && item.relationTo === relation).length ===
0
) {
loadedIDs.push({ id: doc.id, relationTo: relation })
const docTitle = formatUseAsTitle({
collection,
@@ -89,7 +91,10 @@ const optionsReducer = (state: OptionGroup[], action: Action): OptionGroup[] =>
}, [])
ids.forEach((id) => {
if (!loadedIDs.includes(id)) {
if (
loadedIDs.filter((item) => item.id === id && item.relationTo === relation).length === 0
) {
loadedIDs.push({ id, relationTo: relation })
newSubOptions.push({
label: `${i18n.t('general:untitled')} - ID: ${id}`,
relationTo: relation,

View File

@@ -74,21 +74,22 @@ const DefaultCell: React.FC<Props> = (props) => {
if (collection.upload && fieldAffectsData(field) && field.name === 'filename') {
CellComponent = cellComponents.File
} else {
return (
<WrapElement {...wrapElementProps}>
{(cellData === '' || typeof cellData === 'undefined') &&
'label' in field &&
t('noLabel', {
if (!cellData && 'label' in field) {
return (
<WrapElement {...wrapElementProps}>
{t('noLabel', {
label: getTranslation(
typeof field.label === 'function' ? 'data' : field.label || 'data',
i18n,
),
})}
{typeof cellData === 'string' && cellData}
{typeof cellData === 'number' && cellData}
{typeof cellData === 'object' && JSON.stringify(cellData)}
</WrapElement>
)
</WrapElement>
)
} else if (typeof cellData === 'string' || typeof cellData === 'number') {
return <WrapElement {...wrapElementProps}>{cellData}</WrapElement>
} else if (typeof cellData === 'object') {
return <WrapElement {...wrapElementProps}>{JSON.stringify(cellData)}</WrapElement>
}
}
}

View File

@@ -62,6 +62,17 @@ export async function getLocalizedPaths({
return paths
}
if (!matchedField && currentPath === 'id' && i === pathSegments.length - 1) {
lastIncompletePath.path = currentPath
const idField: Field = {
name: 'id',
type: payload.db.defaultIDType as 'text',
}
lastIncompletePath.field = idField
lastIncompletePath.complete = true
return paths
}
if (matchedField) {
if ('hidden' in matchedField && matchedField.hidden && !overrideAccess) {
lastIncompletePath.invalid = true

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "2.2.0",
"version": "2.2.1",
"homepage:": "https://payloadcms.com",
"repository": "git@github.com:payloadcms/plugin-seo.git",
"description": "SEO plugin for Payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "0.6.0",
"version": "0.6.1",
"description": "The officially supported Lexical richtext adapter for Payload",
"repository": "https://github.com/payloadcms/payload",
"license": "MIT",

View File

@@ -49,7 +49,7 @@ const RichText: React.FC<FieldProps> = (props) => {
validate: memoizedValidate,
})
const { errorMessage, setValue, showError, value } = fieldType
const { errorMessage, initialValue, setValue, showError, value } = fieldType
const classes = [
baseClass,
@@ -77,6 +77,7 @@ const RichText: React.FC<FieldProps> = (props) => {
<LexicalProvider
editorConfig={editorConfig}
fieldProps={props}
key={JSON.stringify({ initialValue, path })} // makes sure lexical is completely re-rendered when initialValue changes, bypassing the lexical-internal value memoization. That way, external changes to the form will update the editor. More infos in PR description (https://github.com/payloadcms/payload/pull/5010)
onChange={(editorState) => {
let serializedEditorState = editorState.toJSON()

View File

@@ -326,7 +326,7 @@ const RichText: React.FC<FieldProps> = (props) => {
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
<Slate
editor={editor}
key={JSON.stringify({ initialValue, path })}
key={JSON.stringify({ initialValue, path })} // makes sure slate is completely re-rendered when initialValue changes, bypassing the slate-internal value memoization. That way, external changes to the form will update the editor
onChange={handleChange}
value={valueToRender as any[]}
>

View File

@@ -1145,6 +1145,63 @@ describe('Fields', () => {
expect(existTrueIDs).toContain(hasJSON.id)
expect(existFalseIDs).not.toContain(hasJSON.id)
})
it('exists should not return null values', async () => {
const { id } = await payload.create({
collection: 'select-fields',
data: {
select: 'one',
},
})
const existsResult = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: true },
},
})
expect(existsResult.docs).toHaveLength(1)
const existsFalseResult = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: false },
},
})
expect(existsFalseResult.docs).toHaveLength(0)
await payload.update({
collection: 'select-fields',
id,
data: {
select: null,
},
})
const existsTrueResult = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: true },
},
})
expect(existsTrueResult.docs).toHaveLength(0)
const result = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: false },
},
})
expect(result.docs).toHaveLength(1)
})
})
})
@@ -1249,4 +1306,63 @@ describe('Fields', () => {
expect(query.docs).toBeDefined()
})
})
describe('clearable fields - exists', () => {
it('exists should not return null values', async () => {
const { id } = await payload.create({
collection: 'select-fields',
data: {
select: 'one',
},
})
const existsResult = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: true },
},
})
expect(existsResult.docs).toHaveLength(1)
const existsFalseResult = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: false },
},
})
expect(existsFalseResult.docs).toHaveLength(0)
await payload.update({
collection: 'select-fields',
id,
data: {
select: null,
},
})
const existsTrueResult = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: true },
},
})
expect(existsTrueResult.docs).toHaveLength(0)
const result = await payload.find({
collection: 'select-fields',
where: {
id: { equals: id },
select: { exists: false },
},
})
expect(result.docs).toHaveLength(1)
})
})
})

View File

@@ -273,6 +273,74 @@ describe('Relationships', () => {
expect(query.totalDocs).toEqual(2)
})
// https://github.com/payloadcms/payload/issues/4240
it('should allow querying by relationship id field', async () => {
/**
* This test shows something which breaks on postgres but not on mongodb.
*/
const someDirector = await payload.create({
collection: 'directors',
data: {
name: 'Quentin Tarantino',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Harry Potter',
},
})
await payload.create({
collection: 'movies',
data: {
name: 'Lord of the Rings is boring',
director: someDirector.id,
},
})
// This causes the following error:
// "Your "id" field references a column "directors"."id", but the table "directors" is not part of the query! Did you forget to join it?"
// This only happens on postgres, not on mongodb
const query = await payload.find({
collection: 'movies',
depth: 5,
limit: 1,
where: {
or: [
{
name: {
equals: 'Pulp Fiction',
},
},
{
'director.id': {
equals: someDirector.id,
},
},
],
},
})
expect(query.totalDocs).toEqual(3)
expect(query.docs).toHaveLength(1) // Due to limit: 1
})
describe('Custom ID', () => {
it('should query a custom id relation', async () => {
const { doc } = await client.findByID<Post>({ id: post.id })
@@ -288,7 +356,7 @@ describe('Relationships', () => {
await expect(async () =>
createPost({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Sending bad data to test error handling
// @ts-expect-error Sending bad data to test error handling
customIdRelation: 1234,
}),
).rejects.toThrow('The following field is invalid: customIdRelation')
@@ -298,7 +366,7 @@ describe('Relationships', () => {
await expect(async () =>
createPost({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Sending bad data to test error handling
// @ts-expect-error Sending bad data to test error handling
customIdNumberRelation: 'bad-input',
}),
).rejects.toThrow('The following field is invalid: customIdNumberRelation')