Compare commits
11 Commits
db-postgre
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ea6172afa | ||
|
|
486774796d | ||
|
|
1cd1c38764 | ||
|
|
f6d7da7510 | ||
|
|
cdc4cb971b | ||
|
|
e0191b54e1 | ||
|
|
2315781f18 | ||
|
|
a0a58e7fd2 | ||
|
|
e1813fb884 | ||
|
|
da184d40ec | ||
|
|
ca8675f89d |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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[]}
|
||||
>
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user