Compare commits

..

24 Commits

Author SHA1 Message Date
Elliot DeNolf
769bee82cd chore(release): db-postgres/0.8.10 [skip ci] 2024-11-11 15:19:34 -05:00
Elliot DeNolf
55eb6e7583 chore(release): payload/2.30.4 [skip ci] 2024-11-11 15:18:05 -05:00
Andreas Bernhard
a907480a94 fix: edit many modal draft action button order and style (#9046)
### What?

Fix for See #9045 - this PR adjusts the order and the style of the draft
button to be consistent with the default edit-collection view.

### Why?

If "drafts" are enabled the "draft" action button is rendered on the 
- edit-many-modal: as a "primary" button, on the right side of the
"publish" button
- edit-collection view: as a "secondary" button, to the left of the
"publish" button

--> This is inconsistent and can lead to user mistakes pushing the wrong
button because muscle-memory+human-brains

### How?

Adjusted `EditMany/index.tsx`
 - setting the `buttonStyle` attribute for the `SaveDraft` component 
- switching the positions of the action buttons in the `EditMany`
component

### Fix

 - Before

![image](https://github.com/user-attachments/assets/f20ea03f-99a1-44d5-8402-a26d3daf9e95)

 - After

![image](https://github.com/user-attachments/assets/da556fc1-bf03-47e4-a619-202198df8be1)

---------

Co-authored-by: Patrik Kozak <patrik@payloadcms.com>
2024-11-06 11:20:03 -05:00
Sasha
9c25754eed fix(db-postgres): sort by localized fields (#9016)
### What?
Port of https://github.com/payloadcms/payload/pull/8839 to main

Fixes https://github.com/payloadcms/payload/issues/5152
2024-11-05 12:52:05 -05:00
vahacreative
7c9ec9c4e0 feat(plugin-seo): add Turkish translation (#8918) 2024-11-04 10:35:02 -06:00
Sasha
a0bd7060c4 fix: querying relationships by id path with REST (#9014)
### What?
Port of https://github.com/payloadcms/payload/pull/9013 to main

Fixes https://github.com/payloadcms/payload/issues/9008
2024-11-04 17:57:28 +02:00
Dan Ribbens
169da5c3d8 fix: list drawer relationship not displaying (#9011)
List drawer relationships are not showing when selecting an existing
upload.

Before:

![image](https://github.com/user-attachments/assets/77c68572-31d2-47f9-b754-82808cf3f01c)

After:
![Screenshot 2024-11-04
093830](https://github.com/user-attachments/assets/59f41ee0-f3de-431d-b432-e6181b5736a8)
2024-11-04 10:04:01 -05:00
Elliot DeNolf
25932c0db6 chore(release): create-payload-app/1.1.0 [skip ci] 2024-10-31 09:10:14 -04:00
Elliot DeNolf
3bce3d4240 ci: update github author usernames 2024-10-30 15:33:16 -04:00
Elliot DeNolf
e2a13deff6 chore: remove misc patch 2024-10-30 14:40:00 -04:00
Erik Sachse
5d99adfd38 docs: missing comma (#8939) 2024-10-30 11:54:07 -04:00
Elliot DeNolf
819753a637 chore: stronger required language on design issue template 2024-10-29 12:01:26 -04:00
Elliot DeNolf
b7d65ab717 ci: add custom triage action, label-author into triage workflow 2024-10-29 11:49:04 -04:00
Elliot DeNolf
e365300bee ci: update issue templates 2024-10-29 11:47:13 -04:00
Elliot DeNolf
4d9f494a80 ci: update payload team github usernames 2024-10-26 22:24:33 -04:00
Elliot DeNolf
fa6d4596dc chore: adjust environment info renderer in issue template 2024-10-24 21:31:07 -04:00
Elliot DeNolf
08b02ecba2 chore(cpa): pin monorepo templates to tag (#8857)
In preparation for moving beta branch -> main, pinning the v2 templates
will ensure they are consistent through the process.
2024-10-24 14:42:14 -04:00
Sasha
eba777c3a0 fix: error saving after duplicating blocks with nested items (#8814)
Fixes https://github.com/payloadcms/payload/issues/6583
Port of https://github.com/payloadcms/payload/pull/8790 to 2.0
2024-10-23 16:21:36 -04:00
Elliot DeNolf
bfe6918681 chore: update pull request template 2024-10-22 16:36:05 -04:00
Elliot DeNolf
e5d6cdae38 chore(release): db-postgres/0.8.9 [skip ci] 2024-10-18 15:37:39 -04:00
Elliot DeNolf
218f2ead03 chore(release): payload/2.30.3 [skip ci] 2024-10-18 15:36:55 -04:00
Sasha
e9c1222182 fix(db-postgres): migrate:create errors with previous schemas (#8786)
Fixes https://github.com/payloadcms/payload/issues/8782
2024-10-18 14:04:23 -04:00
Sasha
c8ed6454a7 fix: duplicate with select hasMany fields (#8734)
Fixes https://github.com/payloadcms/payload/issues/6522 by not sending
`id` of the _current_ document to the `post` / `patch` payload. It
caused issues with Postgres and select `hasMany: true`
2024-10-17 16:31:39 -04:00
Elliot DeNolf
4077598777 chore(release): richtext-lexical/0.11.4 [skip ci] 2024-10-17 09:18:09 -04:00
44 changed files with 42707 additions and 203 deletions

View File

@@ -1,6 +1,6 @@
name: Bug Report v3
description: Create a bug report for Payload v3 (beta)
labels: ['status: needs-triage', 'v3']
name: Functionality Bug
description: '[REPRODUCTION REQUIRED] - Create a bug report'
labels: ['status: needs-triage', 'v3', 'validate-reproduction']
body:
- type: textarea
@@ -14,8 +14,8 @@ body:
attributes:
label: Link to the code that reproduces this issue
description: >-
Required: Please provide a link to your reproduction. Note, if the URL is invalid (404 or a private repository), we may close the issue.
Either use `npx create-payload-app@beta -t blank` or follow the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md) for more information.
_REQUIRED_: Please provide a link to your reproduction. Note, if the URL is invalid (404 or a private repository), we may close the issue.
Either use `npx create-payload-app@beta -t blank` then push to a repo or follow the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md) for more information.
validations:
required: true
@@ -33,6 +33,7 @@ body:
options:
- 'Not sure'
- 'area: core'
- 'area: docs'
- 'area: templates'
- 'area: ui'
- 'db-mongodb'
@@ -57,7 +58,7 @@ body:
attributes:
label: Environment Info
description: Paste output from `pnpm payload info` (>= beta.92) _or_ Payload, Node.js, and Next.js versions.
render: bash
render: text
placeholder: |
Payload:
Node.js:

View File

@@ -0,0 +1,41 @@
name: Design Issue
description: '[SCREENSHOT REQUIRED] - Create a design issue report'
labels: ['status: needs-triage', 'v3', 'area: ui']
body:
- type: textarea
attributes:
label: Describe the Bug.
description: >-
_REQUIRED:_ Please a screenshot/video of the issue along with a detailed description of the problem.
validations:
required: true
- type: textarea
attributes:
label: Reproduction Steps
description: Steps to reproduce the behavior, please provide a clear description of how to reproduce the issue, based on the linked minimal reproduction. Screenshots can be provided in the issue body below. If using code blocks, make sure that [syntax highlighting is correct](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) and double check that the rendered preview is not broken.
validations:
required: true
- type: textarea
attributes:
label: Environment Info
description: Paste output from `pnpm payload info` (>= beta.92) _or_ Payload, Node.js, and Next.js versions.
render: text
placeholder: |
Payload:
Node.js:
Next.js:
validations:
required: true
- type: markdown
attributes:
value: Before submitting the issue, go through the steps you've written down to make sure the steps provided are detailed and clear.
- type: markdown
attributes:
value: Contributors should be able to follow the steps provided in order to reproduce the bug.
- type: markdown
attributes:
value: These steps are used to add integration tests to ensure the same issue does not happen again. Thanks in advance!

View File

@@ -1,11 +1,11 @@
name: Bug Report
description: Create a bug report for Payload
name: v2 Bug Report
description: Report a bug for Payload v2. ONLY CRITICAL bugs will be fixed in v2.
labels: ['status: needs-triage', 'v2']
body:
- type: markdown
attributes:
value: |
*Note:* Feature requests should be opened as [discussions](https://github.com/payloadcms/payload/discussions/new?category=feature-requests-ideas).
ONLY CRITICAL bugs will be fixed in v2.
- type: input
id: reproduction-link
attributes:

View File

@@ -1,10 +1,23 @@
<!--
For external contributors, please include:
Thank you for the PR! Please go through the checklist below and make sure you've completed all the steps.
- A summary of the pull request and any related issues it fixes.
- Reasoning for the changes made or any additional context that may be useful.
Please review the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository if you haven't already.
Ensure you have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository.
The following items will ensure that your PR is handled as smoothly as possible:
-->
- PR Title must follow conventional commits format. For example, `feat: my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic behind a change
### What?
### Why?
### How?
Fixes #
-->

13
.github/actions/triage/.eslintrc.js vendored Normal file
View File

@@ -0,0 +1,13 @@
module.exports = {
env: {
es6: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
}

8
.github/actions/triage/.prettierrc.js vendored Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
printWidth: 100,
parser: 'typescript',
semi: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

22
.github/actions/triage/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2024 Payload <info@payloadcms.com>. All modification and additions are copyright of Payload.
---
Original license:
ISC License
Copyright (c) 2023, Balázs Orbán
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

21
.github/actions/triage/README.md vendored Normal file
View File

@@ -0,0 +1,21 @@
# Triage
Modified version of https://github.com/balazsorban44/nissuer
## Modifications
- Port to TypeScript
- Remove issue locking
- Remove reproduction blocklist
- Uses `@vercel/ncc` for packaging
## Development
> [!IMPORTANT]
> Whenever a modification is made to the action, the action built to `dist` must be committed to the repository.
This is done by running:
```sh
pnpm build
```

40
.github/actions/triage/action.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Triage
description: Initial triage for issues
inputs:
reproduction-comment:
description: 'Either a string or a path to a .md file inside the repository. Example: ".github/invalid-reproduction.md"'
default: '.github/invalid-reproduction.md'
reproduction-hosts:
description: 'Comma-separated list of hostnames that are allowed for reproductions. Example: "github.com,codesandbox.io"'
default: github.com
reproduction-invalid-label:
description: 'Label to apply to issues without a valid reproduction. Example: "invalid-reproduction"'
default: 'invalid-reproduction'
reproduction-issue-labels:
description: 'Comma-separated list of issue labels. If configured, only verify reproduction URLs of issues with one of these labels present. Adding a comma at the end will handle non-labeled issues as invalid. Example: "bug,", will consider issues with the label "bug" or no label.'
default: ''
reproduction-link-section:
description: 'A regular expression string with "(.*)" matching a valid URL in the issue body. The result is trimmed. Example: "### Link to reproduction(.*)### To reproduce"'
default: '### Link to reproduction(.*)### To reproduce'
tag-only:
description: Log and tag only. Do not perform closing or commenting actions.
default: false
runs:
using: "composite"
steps:
- name: Checkout code
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v4
- name: Run action
run: node ${{ github.action_path }}/dist/index.js
shell: sh
# https://github.com/actions/runner/issues/665#issuecomment-676581170
env:
"INPUT_REPRODUCTION_COMMENT": ${{inputs.reproduction-comment}}
"INPUT_REPRODUCTION_HOSTS": ${{inputs.reproduction-hosts}}
"INPUT_REPRODUCTION_INVALID_LABEL": ${{inputs.reproduction-invalid-label}}
"INPUT_REPRODUCTION_ISSUE_LABELS": ${{inputs.reproduction-issue-labels}}
"INPUT_REPRODUCTION_LINK_SECTION": ${{inputs.reproduction-link-section}}
"INPUT_TAG_ONLY": ${{inputs.tag-only}}

34048
.github/actions/triage/dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

7
.github/actions/triage/jest.config.js vendored Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
testEnvironment: 'node',
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/dist/'],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest'],
},
}

34
.github/actions/triage/package.json vendored Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "triage",
"version": "0.0.0",
"description": "GitHub Action to triage new issues",
"main": "dist/index.js",
"license": "MIT",
"private": true,
"scripts": {
"clean": "rimraf dist",
"build": "pnpm build:typecheck && pnpm build:ncc",
"build:ncc": "ncc build src/index.ts -t -o dist",
"build:typecheck": "tsc",
"test": "jest"
},
"dependencies": {
"@actions/core": "^1.3.0",
"@actions/github": "^5.0.0"
},
"devDependencies": {
"@octokit/webhooks-types": "^7.5.1",
"@swc/jest": "^0.2.36",
"@types/jest": "^27.5.2",
"@types/node": "^20.16.5",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vercel/ncc": "0.38.1",
"concurrently": "^8.2.2",
"eslint": "^7.32.0",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"ts-jest": "^26.5.6",
"typescript": "^4.9.5"
}
}

5419
.github/actions/triage/pnpm-lock.yaml generated vendored Normal file

File diff suppressed because it is too large Load Diff

195
.github/actions/triage/src/index.ts vendored Normal file
View File

@@ -0,0 +1,195 @@
import { debug, error, getBooleanInput, getInput, info, setFailed } from '@actions/core'
import { context, getOctokit } from '@actions/github'
import { readFile, access } from 'node:fs/promises'
import { join } from 'node:path'
// Ensure GITHUB_TOKEN and GITHUB_WORKSPACE are present
if (!process.env.GITHUB_TOKEN) throw new TypeError('No GITHUB_TOKEN provided')
if (!process.env.GITHUB_WORKSPACE) throw new TypeError('Not a GitHub workspace')
// Define the configuration object
interface Config {
invalidLink: {
comment: string
bugLabels: string[]
hosts: string[]
label: string
linkSection: string
}
tagOnly: boolean
token: string
workspace: string
}
const config: Config = {
invalidLink: {
comment: getInput('reproduction_comment') || '.github/invalid-reproduction.md',
bugLabels: getInput('reproduction_issue_labels')
.split(',')
.map(l => l.trim()),
hosts: (getInput('reproduction_hosts') || 'github.com').split(',').map(h => h.trim()),
label: getInput('reproduction_invalid_label') || 'invalid-reproduction',
linkSection:
getInput('reproduction_link_section') || '### Link to reproduction(.*)### To reproduce',
},
tagOnly: getBooleanOrUndefined('tag_only') || false,
token: process.env.GITHUB_TOKEN,
workspace: process.env.GITHUB_WORKSPACE,
}
// Attempt to parse JSON, return parsed object or error
function tryParse(json: string): Record<string, unknown> {
try {
return JSON.parse(json)
} catch (e) {
setFailed(`Could not parse JSON: ${e instanceof Error ? e.message : e}`)
return {}
}
}
// Retrieves a boolean input or undefined based on environment variables
function getBooleanOrUndefined(value: string): boolean | undefined {
const variable = process.env[`INPUT_${value.toUpperCase()}`]
return variable === undefined || variable === '' ? undefined : getBooleanInput(value)
}
// Returns the appropriate label match type
function getLabelMatch(value: string | undefined): 'name' | 'description' {
return value === 'name' ? 'name' : 'description'
}
// Function to check if an issue contains a valid reproduction link
async function checkValidReproduction(): Promise<void> {
const { issue, action } = context.payload as {
issue: { number: number; body: string; labels: { name: string }[] } | undefined
action: string
}
if (action !== 'opened' || !issue?.body) return
const labels = issue.labels.map(l => l.name)
const issueMatchingLabel =
labels.length &&
config.invalidLink.bugLabels.length &&
labels.some(l => config.invalidLink.bugLabels.includes(l))
if (!issueMatchingLabel) {
info(
`Issue #${issue.number} does not match required labels: ${config.invalidLink.bugLabels.join(', ')}`,
)
info(`Issue labels: ${labels.join(', ')}`)
return
}
info(`Issue #${issue.number} labels: ${labels.join(', ')}`)
const { rest: client } = getOctokit(config.token)
const common = { ...context.repo, issue_number: issue.number }
const labelsToRemove = labels.filter(l => config.invalidLink.bugLabels.includes(l))
if (await isValidReproduction(issue.body)) {
await Promise.all(
labelsToRemove.map(label => client.issues.removeLabel({ ...common, name: label })),
)
return info(`Issue #${issue.number} contains a valid reproduction 💚`)
}
info(`Invalid reproduction, issue will be closed/labeled/commented...`)
// Adjust labels
await Promise.all(
labelsToRemove.map(label => client.issues.removeLabel({ ...common, name: label })),
)
info(`Issue #${issue.number} - validate label removed`)
await client.issues.addLabels({ ...common, labels: [config.invalidLink.label] })
info(`Issue #${issue.number} - labeled`)
// If tagOnly, do not close or comment
if (config.tagOnly) {
info('Tag-only enabled, no closing/commenting actions taken')
return
}
// Perform closing and commenting actions
await client.issues.update({ ...common, state: 'closed' })
info(`Issue #${issue.number} - closed`)
const comment = join(config.workspace, config.invalidLink.comment)
await client.issues.createComment({ ...common, body: await getCommentBody(comment) })
info(`Issue #${issue.number} - commented`)
}
/**
* Determine if an issue contains a valid/accessible link to a reproduction.
*
* Returns `true` if the link is valid.
* @param body - The body content of the issue
*/
async function isValidReproduction(body: string): Promise<boolean> {
const linkSectionRe = new RegExp(config.invalidLink.linkSection, 'is')
const link = body.match(linkSectionRe)?.[1]?.trim()
if (!link) {
info('Missing link')
info(`Link section regex: ${linkSectionRe}`)
info(`Link section: ${body}`)
return false
}
info(`Checking validity of link: ${link}`)
if (!URL.canParse(link)) {
info(`Invalid URL: ${link}`)
return false
}
const url = new URL(link)
if (!config.invalidLink.hosts.includes(url.hostname)) {
info('Link did not match allowed reproduction hosts')
return false
}
try {
// Verify that the link can be accessed
const response = await fetch(link)
const isOk = response.status < 400 || response.status >= 500
info(`Link status: ${response.status}`)
if (!isOk) {
info(`Link returned status ${response.status}`)
}
return isOk
} catch (error) {
info(`Error fetching link: ${(error as Error).message}`)
return false
}
}
/**
* Return either a file's content or a string
* @param {string} pathOrComment
*/
async function getCommentBody(pathOrComment: string) {
try {
await access(pathOrComment)
return await readFile(pathOrComment, 'utf8')
} catch (error: any) {
if (error.code === 'ENOENT') return pathOrComment
throw error
}
}
async function run() {
const { token, workspace, ...safeConfig } = config
info('Configuration:')
info(JSON.stringify(safeConfig, null, 2))
await checkValidReproduction()
}
run().catch(setFailed)

15
.github/actions/triage/tsconfig.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es2020.string"],
"noEmit": true,
"strict": true,
"noUnusedLocals": false, // Undo this
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"downlevelIteration": true,
"skipLibCheck": true,
},
"exclude": ["src/**/*.test.ts"]
}

49
.github/pnpm-lock.yaml generated vendored
View File

@@ -55,6 +55,55 @@ importers:
specifier: ^4.9.5
version: 4.9.5
actions/triage:
dependencies:
'@actions/core':
specifier: ^1.3.0
version: 1.10.1
'@actions/github':
specifier: ^5.0.0
version: 5.1.1
devDependencies:
'@octokit/webhooks-types':
specifier: ^7.5.1
version: 7.5.1
'@swc/jest':
specifier: ^0.2.36
version: 0.2.36(@swc/core@1.7.23)
'@types/jest':
specifier: ^27.5.2
version: 27.5.2
'@types/node':
specifier: ^20.16.5
version: 20.16.5
'@typescript-eslint/eslint-plugin':
specifier: ^4.33.0
version: 4.33.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5)
'@typescript-eslint/parser':
specifier: ^4.33.0
version: 4.33.0(eslint@7.32.0)(typescript@4.9.5)
'@vercel/ncc':
specifier: 0.38.1
version: 0.38.1
concurrently:
specifier: ^8.2.2
version: 8.2.2
eslint:
specifier: ^7.32.0
version: 7.32.0
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@20.16.5)(node-notifier@8.0.2)
prettier:
specifier: ^3.3.3
version: 3.3.3
ts-jest:
specifier: ^26.5.6
version: 26.5.6(jest@29.7.0(@types/node@20.16.5)(node-notifier@8.0.2))(typescript@4.9.5)
typescript:
specifier: ^4.9.5
version: 4.9.5
packages:
'@actions/core@1.10.1':

View File

@@ -1,80 +0,0 @@
name: label-author
on:
pull_request:
types: [opened]
issues:
types: [opened]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
debug-context:
runs-on: ubuntu-latest
steps:
- name: View context attributes
uses: actions/github-script@v7
with:
script: console.log(context)
label-created-by:
name: Label pr/issue on opening
runs-on: ubuntu-latest
steps:
- name: Tag with 'created-by'
uses: actions/github-script@v7
if: github.event.action == 'opened'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const payloadTeamUsernames = [
'denolfe',
'jmikrut',
'DanRibbens',
'jacobsfletch',
'JarrodMFlesch',
'AlessioGr',
'JessChowdhury',
'kendelljoseph',
'PatrikKozak',
'tylandavis',
'paulpopus',
];
const type = context.payload.pull_request ? 'pull_request' : 'issue';
const isTeamMember = payloadTeamUsernames
.map(n => n.toLowerCase())
.includes(context.payload[type].user.login.toLowerCase());
if (isTeamMember) {
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['created-by: Payload team'],
});
console.log(`Added 'created-by: Payload team' label`);
return;
}
const association = context.payload[type].author_association;
let label = ''
if (association === 'MEMBER' || association === 'OWNER') {
label = 'created-by: Payload team';
} else if (association === 'CONTRIBUTOR') {
label = 'created-by: Contributor';
}
if (!label) return;
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [label],
});
console.log(`Added '${label}' label.`);

View File

@@ -1,6 +1,9 @@
name: triage
on:
pull_request:
types:
- opened
issues:
types:
- opened
@@ -9,21 +12,91 @@ env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: read
issues: write
pull-requests: write
jobs:
triage:
name: nissuer
if: false # Disable after adjusting scenarios which this should be applied
debug-context:
runs-on: ubuntu-latest
steps:
- uses: balazsorban44/nissuer@1.10.0
- name: View context attributes
uses: actions/github-script@v7
with:
script: console.log({ context })
label-created-by:
name: label-on-open
runs-on: ubuntu-latest
steps:
- name: Tag with 'created-by'
uses: actions/github-script@v7
if: github.event.action == 'opened'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const payloadTeamUsernames = [
'denolfe',
'jmikrut',
'DanRibbens',
'jacobsfletch',
'JarrodMFlesch',
'AlessioGr',
'JessChowdhury',
'kendelljoseph',
'PatrikKozak',
'tylandavis',
'paulpopus',
'r1tsuu',
'GermanJablo',
];
const type = context.payload.pull_request ? 'pull_request' : 'issue';
const isTeamMember = payloadTeamUsernames
.map(n => n.toLowerCase())
.includes(context.payload[type].user.login.toLowerCase());
if (isTeamMember) {
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['created-by: Payload team'],
});
console.log(`Added 'created-by: Payload team' label`);
return;
}
const association = context.payload[type].author_association;
let label = ''
if (association === 'MEMBER' || association === 'OWNER') {
label = 'created-by: Payload team';
} else if (association === 'CONTRIBUTOR') {
label = 'created-by: Contributor';
}
if (!label) return;
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [label],
});
console.log(`Added '${label}' label.`);
triage:
name: initial-triage
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/triage
with:
label-area-prefix: ""
label-area-match: "name"
label-area-section: 'Which area\(s\) are affected\? \(Select all that apply\)(.*)### Environment Info'
reproduction-comment: '.github/comments/invalid-reproduction.md'
reproduction-blocklist: 'github.com/\\w*/?$,github.com$'
reproduction-link-section: '### Link to the code that reproduces this issue(.*)### Reproduction Steps'
reproduction-invalid-label: 'invalid-reproduction'
reproduction-issue-labels: 'status: needs-triage,'
reproduction-issue-labels: 'validate-reproduction'
tag-only: 'true'

View File

@@ -1,3 +1,27 @@
## [2.30.4](https://github.com/payloadcms/payload/compare/v2.30.3...v2.30.4) (2024-11-11)
### Features
* **plugin-seo:** add Turkish translation ([#8918](https://github.com/payloadcms/payload/issues/8918)) ([7c9ec9c](https://github.com/payloadcms/payload/commit/7c9ec9c4e0bafd19eb0ef94e9bb4aa07df3e097e))
### Bug Fixes
* **db-postgres:** sort by localized fields ([#9016](https://github.com/payloadcms/payload/issues/9016)) ([9c25754](https://github.com/payloadcms/payload/commit/9c25754eed87e70b053231693102378150b8b80d))
* edit many modal draft action button order and style ([#9046](https://github.com/payloadcms/payload/issues/9046)) ([a907480](https://github.com/payloadcms/payload/commit/a907480a94f07b4afa0e0723a3738c8bd457c51f)), closes [#9045](https://github.com/payloadcms/payload/issues/9045)
* error saving after duplicating blocks with nested items ([#8814](https://github.com/payloadcms/payload/issues/8814)) ([eba777c](https://github.com/payloadcms/payload/commit/eba777c3a0b4c489d87d3c6154d835a105fef4a2))
* list drawer relationship not displaying ([#9011](https://github.com/payloadcms/payload/issues/9011)) ([169da5c](https://github.com/payloadcms/payload/commit/169da5c3d83bfe3559227504929aa62721ded1d9))
* querying relationships by `id` path with REST ([#9014](https://github.com/payloadcms/payload/issues/9014)) ([a0bd706](https://github.com/payloadcms/payload/commit/a0bd7060c45df3037ee074bd83400837c0adb83a))
## [2.30.3](https://github.com/payloadcms/payload/compare/v2.30.2...v2.30.3) (2024-10-18)
### Bug Fixes
* **db-postgres:** migrate:create errors with previous schemas ([#8786](https://github.com/payloadcms/payload/issues/8786)) ([e9c1222](https://github.com/payloadcms/payload/commit/e9c12221824a9a180991722135d22ff91d07ef11))
* duplicate with select hasMany fields ([#8734](https://github.com/payloadcms/payload/issues/8734)) ([c8ed645](https://github.com/payloadcms/payload/commit/c8ed6454a733bea09ae620517c4894701999e119))
## [2.30.2](https://github.com/payloadcms/payload/compare/v2.30.1...v2.30.2) (2024-10-17)
### Bug Fixes

View File

@@ -66,8 +66,8 @@ export default buildConfig({
admin: {
bundler: webpackBundler(), // or viteBundler()
},
db: mongooseAdapter({}) // or postgresAdapter({}),
editor: lexicalEditor({}) // or slateEditor({})
db: mongooseAdapter({}), // or postgresAdapter({})
editor: lexicalEditor({}), // or slateEditor({})
collections: [
{
slug: 'pages',

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "1.0.0",
"version": "1.1.0",
"license": "MIT",
"homepage": "https://payloadcms.com",
"bin": {

View File

@@ -16,38 +16,38 @@ export function getValidTemplates(): ProjectTemplate[] {
return [
{
name: 'blank',
description: 'Blank Template',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/blank',
description: 'Blank Template',
url: 'https://github.com/payloadcms/payload/templates/blank#v2.30.3',
},
{
name: 'website',
description: 'Website Template',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/website',
description: 'Website Template',
url: 'https://github.com/payloadcms/payload/templates/website#v2.30.3',
},
{
name: 'ecommerce',
description: 'E-commerce Template',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
description: 'E-commerce Template',
url: 'https://github.com/payloadcms/payload/templates/ecommerce#v2.30.3',
},
{
name: 'plugin',
description: 'Template for creating a Payload plugin',
type: 'plugin',
description: 'Template for creating a Payload plugin',
url: 'https://github.com/payloadcms/payload-plugin-template',
},
{
name: 'payload-demo',
description: 'Payload demo site at https://demo.payloadcms.com',
type: 'starter',
description: 'Payload demo site at https://demo.payloadcms.com',
url: 'https://github.com/payloadcms/public-demo',
},
{
name: 'payload-website',
description: 'Payload website CMS at https://payloadcms.com',
type: 'starter',
description: 'Payload website CMS at https://payloadcms.com',
url: 'https://github.com/payloadcms/website-cms',
},
]

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "0.8.8",
"version": "0.8.10",
"description": "The officially supported Postgres database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -1,7 +1,7 @@
import type { Count } from 'payload/database'
import type { SanitizedCollectionConfig } from 'payload/types'
import { sql, count as sqlCount } from 'drizzle-orm'
import { count as sqlCount } from 'drizzle-orm'
import toSnakeCase from 'to-snake-case'
import type { ChainedMethods } from './find/chainMethods'
@@ -51,11 +51,7 @@ export const count: Count = async function count(
methods: selectCountMethods,
query: db
.select({
count:
selectCountMethods.length > 0
? sql<number>`count
(DISTINCT ${this.tables[tableName].id})`
: sqlCount(),
count: sqlCount(),
})
.from(table)
.where(where),

View File

@@ -61,7 +61,7 @@ export const createMigration: CreateMigration = async function createMigration(
fs.mkdirSync(dir)
}
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/api')
const { generateDrizzleJson, generateMigration, upPgSnapshot } = require('drizzle-kit/api')
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
@@ -99,6 +99,11 @@ export const createMigration: CreateMigration = async function createMigration(
}
const drizzleJsonAfter = generateDrizzleJson(this.schema)
if (drizzleJsonBefore.version < drizzleJsonAfter.version) {
drizzleJsonBefore = upPgSnapshot(drizzleJsonBefore)
}
const sqlStatementsUp = await generateMigration(drizzleJsonBefore, drizzleJsonAfter)
const sqlStatementsDown = await generateMigration(drizzleJsonAfter, drizzleJsonBefore)

View File

@@ -1,7 +1,7 @@
import type { FindArgs } from 'payload/database'
import type { Field, PayloadRequest, TypeWithID } from 'payload/types'
import { inArray, sql, count as sqlCount } from 'drizzle-orm'
import { inArray, count as sqlCount } from 'drizzle-orm'
import type { PostgresAdapter } from '../types'
import type { ChainedMethods } from './chainMethods'
@@ -143,11 +143,7 @@ export const findMany = async function find({
methods: selectCountMethods,
query: db
.select({
count:
selectCountMethods.length > 0
? sql<number>`count
(DISTINCT ${adapter.tables[tableName].id})`
: sqlCount(),
count: sqlCount(),
})
.from(table)
.where(where),

View File

@@ -185,17 +185,12 @@ export const getTableColumnFromPath = ({
if (locale && field.localized && adapter.payload.config.localization) {
newTableName = `${tableName}${adapter.localesSuffix}`
joins[tableName] = eq(
adapter.tables[tableName].id,
adapter.tables[newTableName]._parentID,
)
let condition = eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID)
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
}
joins[tableName] = condition
}
return getTableColumnFromPath({
adapter,
@@ -224,17 +219,15 @@ export const getTableColumnFromPath = ({
)
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
const conditions = [
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName]._locale, locale),
)
]
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
joins[newTableName] = and(...conditions)
} else {
joins[newTableName] = eq(
adapter.tables[tableName].id,
@@ -268,17 +261,12 @@ export const getTableColumnFromPath = ({
]
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
...joinConstraints,
eq(adapter.tables[newTableName]._locale, locale),
)
const conditions = [...joinConstraints]
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: adapter.tables[newTableName],
value: locale,
})
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
joins[newTableName] = and(...conditions)
} else {
joins[newTableName] = and(...joinConstraints)
}
@@ -302,17 +290,12 @@ export const getTableColumnFromPath = ({
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
eq(arrayParentTable.id, adapter.tables[newTableName]._parentID),
eq(adapter.tables[newTableName]._locale, locale),
)
const conditions = [eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)]
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
joins[newTableName] = and(...conditions)
} else {
joins[newTableName] = eq(arrayParentTable.id, adapter.tables[newTableName]._parentID)
}
@@ -399,17 +382,18 @@ export const getTableColumnFromPath = ({
constraints = constraints.concat(blockConstraints)
selectFields = { ...selectFields, ...blockSelectFields }
if (field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
eq(adapter.tables[newTableName]._locale, locale),
)
if (locale) {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
const conditions = [
eq(
(aliasTable || adapter.tables[tableName]).id,
adapter.tables[newTableName]._parentID,
),
]
if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
}
joins[newTableName] = and(...conditions)
} else {
joins[newTableName] = eq(
adapter.tables[tableName].id,
@@ -444,21 +428,20 @@ export const getTableColumnFromPath = ({
// Join in the relationships table
if (locale && field.localized && adapter.payload.config.localization) {
const conditions = [
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
eq(aliasRelationshipTable.locale, locale),
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
]
if (locale !== 'all') {
conditions.push(eq(aliasRelationshipTable.locale, locale))
}
joinAliases.push({
condition: and(
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
eq(aliasRelationshipTable.locale, locale),
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
),
condition: and(...conditions),
table: aliasRelationshipTable,
})
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: aliasRelationshipTable,
value: locale,
})
}
} else {
// Join in the relationships table
joinAliases.push({
@@ -554,17 +537,14 @@ export const getTableColumnFromPath = ({
const parentTable = aliasTable || adapter.tables[tableName]
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
aliasTable = undefined
let condition = eq(parentTable.id, adapter.tables[newTableName]._parentID)
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
condition = and(condition, eq(adapter.tables[newTableName]._locale, locale))
}
joins[newTableName] = condition
aliasTable = undefined
}
const targetTable = aliasTable || adapter.tables[newTableName]

View File

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

View File

@@ -76,6 +76,8 @@ const Duplicate: React.FC<Props> = ({ id, slug, collection }) => {
})
}
delete data['id']
if (!duplicateID) {
if ('createdAt' in data) delete data.createdAt
if ('updatedAt' in data) delete data.updatedAt

View File

@@ -77,7 +77,7 @@ const SaveDraft: React.FC<{ action: string; disabled: boolean }> = ({ action, di
}, [action, submit])
return (
<FormSubmit className={`${baseClass}__draft`} disabled={disabled} onClick={save}>
<FormSubmit buttonStyle="secondary" className={`${baseClass}__draft`} disabled={disabled} onClick={save}>
{t('saveDraft')}
</FormSubmit>
)
@@ -147,11 +147,11 @@ const EditMany: React.FC<Props> = (props) => {
<div className={`${baseClass}__document-actions`}>
{collection.versions ? (
<React.Fragment>
<Publish
<SaveDraft
action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
disabled={selected.length === 0}
/>
<SaveDraft
<Publish
action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
disabled={selected.length === 0}
/>

View File

@@ -228,6 +228,15 @@ export function fieldReducer(state: Fields, action: FieldAction): Fields {
const duplicateRowState = deepCopyObject(rows[rowIndex])
if (duplicateRowState.id) duplicateRowState.id = new ObjectID().toHexString()
for (const key of Object.keys(duplicateRowState).filter((key) => key.endsWith('.id'))) {
const idState = duplicateRowState[key]
if (idState && typeof idState.value === 'string' && ObjectID.isValid(idState.value)) {
duplicateRowState[key].value = new ObjectID().toHexString()
duplicateRowState[key].initialValue = new ObjectID().toHexString()
}
}
// If there are subfields
if (Object.keys(duplicateRowState).length > 0) {
// Add new object containing subfield names to unflattenedRows array

View File

@@ -48,6 +48,11 @@ const RelationshipCell: React.FC<CellComponentProps<RelationshipField | UploadFi
relationTo: field.relationTo,
value: cell,
})
} else if (typeof cell.id !== 'undefined' && typeof field.relationTo === 'string') {
formattedValues.push({
relationTo: field.relationTo,
value: cell.id,
})
}
})
getRelationships(formattedValues)

View File

@@ -74,6 +74,19 @@ export async function validateSearchParam({
})
}
const promises = []
// Sanitize relation.otherRelation.id to relation.otherRelation
if (paths.at(-1)?.path === 'id') {
const previousField = paths.at(-2)?.field
if (
previousField &&
(previousField.type === 'relationship' || previousField.type === 'upload') &&
typeof previousField.relationTo === 'string'
) {
paths.pop()
}
}
promises.push(
...paths.map(async ({ collectionSlug, field, invalid, path }, i) => {
if (invalid) {
@@ -110,6 +123,7 @@ export async function validateSearchParam({
if (field.type === 'relationship' && Array.isArray(field.relationTo)) {
fieldPath = fieldPath.replace('.value', '')
}
const entityType: 'collections' | 'globals' = globalConfig ? 'globals' : 'collections'
const entitySlug = collectionSlug || globalConfig.slug
const segments = fieldPath.split('.')

View File

@@ -4,6 +4,7 @@ import fa from './fa.json'
import fr from './fr.json'
import nb from './nb.json'
import pl from './pl.json'
import tr from './tr.json'
import ua from './ua.json'
import zh from './zh.json'
import zhTw from './zh-tw.json'
@@ -14,7 +15,8 @@ export default {
fa,
fr,
nb,
pl,
pl,
tr,
ua,
zh,
zhTw,

View File

@@ -0,0 +1,23 @@
{
"$schema": "./translation-schema.json",
"plugin-seo": {
"autoGenerate": "Otomatik oluştur",
"imageAutoGenerationTip": "Otomatik oluşturma, seçilen ana görseli alacaktır.",
"bestPractices": "en iyi uygulamalar",
"lengthTipTitle": "{{minLength}} ile {{maxLength}} karakter arasında olmalıdır. Kaliteli meta başlıkları yazmak için yardım almak isterseniz, bkz. ",
"lengthTipDescription": "{{minLength}} ile {{maxLength}} karakter arasında olmalıdır. Kaliteli meta açıklamaları yazmak için yardım almak isterseniz, bkz. ",
"good": "İyi",
"tooLong": "Çok uzun",
"tooShort": "Çok kısa",
"almostThere": "Neredeyse tamam",
"characterCount": "{{current}}/{{minLength}}-{{maxLength}} karakter, ",
"charactersToGo": "{{characters}} karakter kaldı",
"charactersLeftOver": "{{characters}} karakter kaldı",
"charactersTooMany": "{{characters}} karakter fazla",
"noImage": "Görsel yok",
"checksPassing": "{{current}}/{{max}} kontrol geçiyor",
"preview": "Önizleme",
"previewDescription": "Kesin sonuç listeleri içeriğe ve arama alaka düzeyine bağlı olarak değişebilir."
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "0.11.3",
"version": "0.11.4",
"description": "The officially supported Lexical richtext adapter for Payload",
"repository": {
"type": "git",

View File

@@ -1 +1,4 @@
migrations
v5_migrations/*
!v5_migrations/20241018_162142_test_v5.ts
!v5_migrations/20241018_162142_test_v5.json

View File

@@ -59,7 +59,7 @@ describe('database', () => {
})
afterAll(() => {
removeFiles(path.normalize(payload.db.migrationDir))
removeFiles(path.normalize(payload.db.migrationDir), (name) => !name.includes('test_v5'))
})
it('should run migrate:create', async () => {
@@ -74,6 +74,32 @@ describe('database', () => {
expect(migrationFile).toContain('_test')
})
it('should run migrate:create with older drizzle version schema', async () => {
const db = payload.db as unknown as PostgresAdapter
// eslint-disable-next-line jest/no-if
if (db.name !== 'postgres') return
// eslint-disable-next-line jest/no-if
if (db.schemaName && db.schemaName !== 'public') {
return
}
const args = {
_: ['migrate:create', 'test'],
forceAcceptWarning: true,
}
const ogMigrationDir = payload.db.migrationDir
payload.db.migrationDir = path.resolve(__dirname, 'v5_migrations')
await migrate(args)
// read files names in migrationsDir
const migrationFile = path.normalize(fs.readdirSync(payload.db.migrationDir)[2])
expect(migrationFile).toContain('_test')
removeFiles(path.normalize(payload.db.migrationDir), (name) => !name.includes('test_v5'))
payload.db.migrationDir = ogMigrationDir
})
it('should run migrate', async () => {
const args = {
_: ['migrate'],

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,476 @@
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'
import { sql } from 'drizzle-orm'
export async function up({ payload }: MigrateUpArgs): Promise<void> {
await payload.db.drizzle.execute(sql`
DO $$ BEGIN
CREATE TYPE "_locales" AS ENUM('en', 'es');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
CREATE TYPE "selectEnum" AS ENUM('a', 'b', 'c');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
CREATE TYPE "radioEnum" AS ENUM('a', 'b', 'c');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
CREATE TYPE "enum_customs_status" AS ENUM('draft', 'published');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
CREATE TYPE "enum__customs_v_version_status" AS ENUM('draft', 'published');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
CREATE TABLE IF NOT EXISTS "posts" (
"id" serial PRIMARY KEY NOT NULL,
"title" varchar NOT NULL,
"throw_after_change" boolean,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS "relation_a" (
"id" serial PRIMARY KEY NOT NULL,
"rich_text" jsonb,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS "relation_a_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"relation_b_id" integer
);
CREATE TABLE IF NOT EXISTS "relation_b" (
"id" serial PRIMARY KEY NOT NULL,
"rich_text" jsonb,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS "relation_b_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"relation_a_id" integer
);
CREATE TABLE IF NOT EXISTS "customs_customSelect" (
"order" integer NOT NULL,
"parent_id" integer NOT NULL,
"value" "selectEnum",
"id" serial PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS "customArrays" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"text" varchar
);
CREATE TABLE IF NOT EXISTS "customArrays_locales" (
"localized_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL,
CONSTRAINT "customArrays_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
);
CREATE TABLE IF NOT EXISTS "customBlocks" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"text" varchar,
"block_name" varchar
);
CREATE TABLE IF NOT EXISTS "customBlocks_locales" (
"localized_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL,
CONSTRAINT "customBlocks_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
);
CREATE TABLE IF NOT EXISTS "customs" (
"id" serial PRIMARY KEY NOT NULL,
"text" varchar,
"radio" "radioEnum",
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"_status" "enum_customs_status"
);
CREATE TABLE IF NOT EXISTS "customs_locales" (
"localized_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL,
CONSTRAINT "customs_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
);
CREATE TABLE IF NOT EXISTS "customs_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"relation_a_id" integer
);
CREATE TABLE IF NOT EXISTS "_customs_v_version_customSelect" (
"order" integer NOT NULL,
"parent_id" integer NOT NULL,
"value" "selectEnum",
"id" serial PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS "_customArrays_v" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"text" varchar,
"_uuid" varchar
);
CREATE TABLE IF NOT EXISTS "_customArrays_v_locales" (
"localized_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL,
CONSTRAINT "_customArrays_v_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
);
CREATE TABLE IF NOT EXISTS "_customBlocks_v" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"text" varchar,
"_uuid" varchar,
"block_name" varchar
);
CREATE TABLE IF NOT EXISTS "_customBlocks_v_locales" (
"localized_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL,
CONSTRAINT "_customBlocks_v_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
);
CREATE TABLE IF NOT EXISTS "_customs_v" (
"id" serial PRIMARY KEY NOT NULL,
"version_text" varchar,
"version_radio" "radioEnum",
"version_updated_at" timestamp(3) with time zone,
"version_created_at" timestamp(3) with time zone,
"version__status" "enum__customs_v_version_status",
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"latest" boolean
);
CREATE TABLE IF NOT EXISTS "_customs_v_locales" (
"version_localized_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL,
CONSTRAINT "_customs_v_locales_locale_parent_id_unique" UNIQUE("_locale","_parent_id")
);
CREATE TABLE IF NOT EXISTS "_customs_v_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"customs_id" integer,
"relation_a_id" integer
);
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"email" varchar NOT NULL,
"reset_password_token" varchar,
"reset_password_expiration" timestamp(3) with time zone,
"salt" varchar,
"hash" varchar,
"login_attempts" numeric,
"lock_until" timestamp(3) with time zone
);
CREATE TABLE IF NOT EXISTS "payload_preferences" (
"id" serial PRIMARY KEY NOT NULL,
"key" varchar,
"value" jsonb,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS "payload_preferences_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"users_id" integer
);
CREATE TABLE IF NOT EXISTS "payload_migrations" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar,
"batch" numeric,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS "customGlobal" (
"id" serial PRIMARY KEY NOT NULL,
"text" varchar,
"updated_at" timestamp(3) with time zone,
"created_at" timestamp(3) with time zone
);
CREATE TABLE IF NOT EXISTS "_customGlobal_v" (
"id" serial PRIMARY KEY NOT NULL,
"version_text" varchar,
"version_updated_at" timestamp(3) with time zone,
"version_created_at" timestamp(3) with time zone,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE INDEX IF NOT EXISTS "posts_created_at_idx" ON "posts" ("created_at");
CREATE INDEX IF NOT EXISTS "relation_a_created_at_idx" ON "relation_a" ("created_at");
CREATE INDEX IF NOT EXISTS "relation_a_rels_order_idx" ON "relation_a_rels" ("order");
CREATE INDEX IF NOT EXISTS "relation_a_rels_parent_idx" ON "relation_a_rels" ("parent_id");
CREATE INDEX IF NOT EXISTS "relation_a_rels_path_idx" ON "relation_a_rels" ("path");
CREATE INDEX IF NOT EXISTS "relation_b_created_at_idx" ON "relation_b" ("created_at");
CREATE INDEX IF NOT EXISTS "relation_b_rels_order_idx" ON "relation_b_rels" ("order");
CREATE INDEX IF NOT EXISTS "relation_b_rels_parent_idx" ON "relation_b_rels" ("parent_id");
CREATE INDEX IF NOT EXISTS "relation_b_rels_path_idx" ON "relation_b_rels" ("path");
CREATE INDEX IF NOT EXISTS "customs_customSelect_order_idx" ON "customs_customSelect" ("order");
CREATE INDEX IF NOT EXISTS "customs_customSelect_parent_idx" ON "customs_customSelect" ("parent_id");
CREATE INDEX IF NOT EXISTS "customArrays_order_idx" ON "customArrays" ("_order");
CREATE INDEX IF NOT EXISTS "customArrays_parent_id_idx" ON "customArrays" ("_parent_id");
CREATE INDEX IF NOT EXISTS "customBlocks_order_idx" ON "customBlocks" ("_order");
CREATE INDEX IF NOT EXISTS "customBlocks_parent_id_idx" ON "customBlocks" ("_parent_id");
CREATE INDEX IF NOT EXISTS "customBlocks_path_idx" ON "customBlocks" ("_path");
CREATE INDEX IF NOT EXISTS "customs_created_at_idx" ON "customs" ("created_at");
CREATE INDEX IF NOT EXISTS "customs__status_idx" ON "customs" ("_status");
CREATE INDEX IF NOT EXISTS "customs_rels_order_idx" ON "customs_rels" ("order");
CREATE INDEX IF NOT EXISTS "customs_rels_parent_idx" ON "customs_rels" ("parent_id");
CREATE INDEX IF NOT EXISTS "customs_rels_path_idx" ON "customs_rels" ("path");
CREATE INDEX IF NOT EXISTS "_customs_v_version_customSelect_order_idx" ON "_customs_v_version_customSelect" ("order");
CREATE INDEX IF NOT EXISTS "_customs_v_version_customSelect_parent_idx" ON "_customs_v_version_customSelect" ("parent_id");
CREATE INDEX IF NOT EXISTS "_customArrays_v_order_idx" ON "_customArrays_v" ("_order");
CREATE INDEX IF NOT EXISTS "_customArrays_v_parent_id_idx" ON "_customArrays_v" ("_parent_id");
CREATE INDEX IF NOT EXISTS "_customBlocks_v_order_idx" ON "_customBlocks_v" ("_order");
CREATE INDEX IF NOT EXISTS "_customBlocks_v_parent_id_idx" ON "_customBlocks_v" ("_parent_id");
CREATE INDEX IF NOT EXISTS "_customBlocks_v_path_idx" ON "_customBlocks_v" ("_path");
CREATE INDEX IF NOT EXISTS "_customs_v_version_version_created_at_idx" ON "_customs_v" ("version_created_at");
CREATE INDEX IF NOT EXISTS "_customs_v_version_version__status_idx" ON "_customs_v" ("version__status");
CREATE INDEX IF NOT EXISTS "_customs_v_created_at_idx" ON "_customs_v" ("created_at");
CREATE INDEX IF NOT EXISTS "_customs_v_updated_at_idx" ON "_customs_v" ("updated_at");
CREATE INDEX IF NOT EXISTS "_customs_v_latest_idx" ON "_customs_v" ("latest");
CREATE INDEX IF NOT EXISTS "_customs_v_rels_order_idx" ON "_customs_v_rels" ("order");
CREATE INDEX IF NOT EXISTS "_customs_v_rels_parent_idx" ON "_customs_v_rels" ("parent_id");
CREATE INDEX IF NOT EXISTS "_customs_v_rels_path_idx" ON "_customs_v_rels" ("path");
CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" ("created_at");
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" ("email");
CREATE INDEX IF NOT EXISTS "payload_preferences_key_idx" ON "payload_preferences" ("key");
CREATE INDEX IF NOT EXISTS "payload_preferences_created_at_idx" ON "payload_preferences" ("created_at");
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_order_idx" ON "payload_preferences_rels" ("order");
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" ("parent_id");
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_path_idx" ON "payload_preferences_rels" ("path");
CREATE INDEX IF NOT EXISTS "payload_migrations_created_at_idx" ON "payload_migrations" ("created_at");
DO $$ BEGIN
ALTER TABLE "relation_a_rels" ADD CONSTRAINT "relation_a_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "relation_a_rels" ADD CONSTRAINT "relation_a_rels_relation_b_fk" FOREIGN KEY ("relation_b_id") REFERENCES "relation_b"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "relation_b_rels" ADD CONSTRAINT "relation_b_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "relation_b"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "relation_b_rels" ADD CONSTRAINT "relation_b_rels_relation_a_fk" FOREIGN KEY ("relation_a_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customs_customSelect" ADD CONSTRAINT "customs_customSelect_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customArrays" ADD CONSTRAINT "customArrays_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customArrays_locales" ADD CONSTRAINT "customArrays_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customArrays"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customBlocks" ADD CONSTRAINT "customBlocks_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customBlocks_locales" ADD CONSTRAINT "customBlocks_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customBlocks"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customs_locales" ADD CONSTRAINT "customs_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customs_rels" ADD CONSTRAINT "customs_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "customs_rels" ADD CONSTRAINT "customs_rels_relation_a_fk" FOREIGN KEY ("relation_a_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customs_v_version_customSelect" ADD CONSTRAINT "_customs_v_version_customSelect_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customArrays_v" ADD CONSTRAINT "_customArrays_v_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customArrays_v_locales" ADD CONSTRAINT "_customArrays_v_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customArrays_v"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customBlocks_v" ADD CONSTRAINT "_customBlocks_v_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customBlocks_v_locales" ADD CONSTRAINT "_customBlocks_v_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customBlocks_v"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customs_v_locales" ADD CONSTRAINT "_customs_v_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customs_v_rels" ADD CONSTRAINT "_customs_v_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "_customs_v"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customs_v_rels" ADD CONSTRAINT "_customs_v_rels_custom_schema_fk" FOREIGN KEY ("customs_id") REFERENCES "customs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "_customs_v_rels" ADD CONSTRAINT "_customs_v_rels_relation_a_fk" FOREIGN KEY ("relation_a_id") REFERENCES "relation_a"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
`)
}
export async function down({ payload }: MigrateDownArgs): Promise<void> {
await payload.db.drizzle.execute(sql`
DROP TABLE "posts";
DROP TABLE "relation_a";
DROP TABLE "relation_a_rels";
DROP TABLE "relation_b";
DROP TABLE "relation_b_rels";
DROP TABLE "customs_customSelect";
DROP TABLE "customArrays";
DROP TABLE "customArrays_locales";
DROP TABLE "customBlocks";
DROP TABLE "customBlocks_locales";
DROP TABLE "customs";
DROP TABLE "customs_locales";
DROP TABLE "customs_rels";
DROP TABLE "_customs_v_version_customSelect";
DROP TABLE "_customArrays_v";
DROP TABLE "_customArrays_v_locales";
DROP TABLE "_customBlocks_v";
DROP TABLE "_customBlocks_v_locales";
DROP TABLE "_customs_v";
DROP TABLE "_customs_v_locales";
DROP TABLE "_customs_v_rels";
DROP TABLE "users";
DROP TABLE "payload_preferences";
DROP TABLE "payload_preferences_rels";
DROP TABLE "payload_migrations";
DROP TABLE "customGlobal";
DROP TABLE "_customGlobal_v";`)
}

View File

@@ -767,6 +767,39 @@ describe('fields', () => {
await expect(page.locator('.Toastify')).toContainText('Please correct invalid fields')
})
test('should duplicate block', async () => {
await page.goto(url.create)
const firstRow = page.locator('#field-blocks #blocks-row-0')
const rowActions = firstRow.locator('.collapsible__actions')
await expect(rowActions).toBeVisible()
await rowActions.locator('.array-actions__button').click()
const duplicateButton = rowActions.locator('.array-actions__action.array-actions__duplicate')
await expect(duplicateButton).toBeVisible()
await duplicateButton.click()
const blocks = page.locator('#field-blocks > .blocks-field__rows > div')
expect(await blocks.count()).toEqual(4)
})
test('should save when duplicating subblocks', async () => {
await page.goto(url.create)
const subblocksRow = page.locator('#field-blocks #blocks-row-2')
const rowActions = subblocksRow.locator('.collapsible__actions').first()
await expect(rowActions).toBeVisible()
await rowActions.locator('.array-actions__button').click()
const duplicateButton = rowActions.locator('.array-actions__action.array-actions__duplicate')
await expect(duplicateButton).toBeVisible()
await duplicateButton.click()
const blocks = page.locator('#field-blocks > .blocks-field__rows > div')
expect(await blocks.count()).toEqual(4)
await page.click('#action-save')
await expect(page.locator('.Toastify')).toContainText('successfully')
})
describe('row manipulation', () => {
describe('react hooks', () => {
test('should add 2 new block rows', async () => {

View File

@@ -1,9 +1,10 @@
import fs from 'fs'
const removeFiles = (dir) => {
const removeFiles = (dir, nameFilter?: (name: string) => boolean) => {
if (!fs.existsSync(dir)) return
fs.readdirSync(dir).forEach((f) => {
if (nameFilter && !nameFilter(f)) return
return fs.rmSync(`${dir}/${f}`, { recursive: true })
})
}

View File

@@ -2,7 +2,7 @@ import { GraphQLClient } from 'graphql-request'
import type { Config } from '../../packages/payload/src/config/types'
import type { Where } from '../../packages/payload/src/types'
import type { LocalizedPost, WithLocalizedRelationship } from './payload-types'
import type { LocalizedPost, LocalizedSort, WithLocalizedRelationship } from './payload-types'
import payload from '../../packages/payload/src'
import { devUser } from '../credentials'
@@ -359,6 +359,7 @@ describe('Localization', () => {
describe('Localized Sort Count', () => {
const expectedTotalDocs = 5
const posts: LocalizedSort[] = []
beforeAll(async () => {
for (let i = 1; i <= expectedTotalDocs; i++) {
const post = await payload.create({
@@ -370,6 +371,8 @@ describe('Localization', () => {
locale: englishLocale,
})
posts.push(post)
await payload.update({
id: post.id,
collection: localizedSortSlug,
@@ -409,6 +412,101 @@ describe('Localization', () => {
expect(sortByTitleQuery.totalDocs).toEqual(expectedTotalDocs)
expect(sortByDateQuery.totalDocs).toEqual(expectedTotalDocs)
})
it('should return correct order when sorted by localized fields', async () => {
const { docs: docsAsc } = await payload.find({ collection: localizedSortSlug, sort: 'title' })
docsAsc.forEach((doc, i) => {
expect(posts[i].id).toBe(doc.id)
})
const { docs: docsDesc } = await payload.find({
collection: localizedSortSlug,
sort: '-title',
})
docsDesc.forEach((doc, i) => {
expect(posts.at(posts.length - i - 1).id).toBe(doc.id)
})
// Test with words
const randomWords = [
'sunset',
'whisper',
'lighthouse',
'harmony',
'crystal',
'thunder',
'meadow',
'voyage',
'echo',
'quicksand',
]
const randomWordsSpanish = [
'atardecer',
'susurro',
'faro',
'armonía',
'cristal',
'trueno',
'pradera',
'viaje',
'eco',
'arenas movedizas',
]
expect(randomWords).toHaveLength(randomWordsSpanish.length)
const randomWordsPosts: (number | string)[] = []
for (let i = 0; i < randomWords.length; i++) {
const en = randomWords[i]
const post = await payload.create({ collection: 'localized-sort', data: { title: en } })
const es = randomWordsSpanish[i]
await payload.update({
collection: 'localized-sort',
data: { title: es },
id: post.id,
locale: 'es',
})
randomWordsPosts.push(post.id)
}
const ascSortedWordsEn = randomWords.toSorted((a, b) => a.localeCompare(b))
const descSortedWordsEn = randomWords.toSorted((a, b) => b.localeCompare(a))
const q = { id: { in: randomWordsPosts } }
const { docs: randomWordsEnAsc } = await payload.find({
collection: localizedSortSlug,
sort: 'title',
where: q,
})
randomWordsEnAsc.forEach((doc, i) => {
expect(ascSortedWordsEn[i]).toBe(doc.title)
})
const { docs: randomWordsEnDesc } = await payload.find({
collection: localizedSortSlug,
sort: '-title',
where: q,
})
randomWordsEnDesc.forEach((doc, i) => {
expect(descSortedWordsEn[i]).toBe(doc.title)
})
// Test sorting for Spanish locale
const ascSortedWordsEs = randomWordsSpanish.toSorted((a, b) => a.localeCompare(b))
const descSortedWordsEs = randomWordsSpanish.toSorted((a, b) => b.localeCompare(a))
// Fetch sorted words in Spanish (ascending)
const { docs: randomWordsEsAsc } = await payload.find({
collection: localizedSortSlug,
sort: 'title',
where: q,
locale: 'es',
})
randomWordsEsAsc.forEach((doc, i) => {
expect(ascSortedWordsEs[i]).toBe(doc.title)
})
// Fetch sorted words in Spanish (descending)
const { docs: randomWordsEsDesc } = await payload.find({
collection: localizedSortSlug,
sort: '-title',
where: q,
locale: 'es',
})
randomWordsEsDesc.forEach((doc, i) => {
expect(descSortedWordsEs[i]).toBe(doc.title)
})
})
})
describe('Localized Relationship', () => {

View File

@@ -667,6 +667,32 @@ describe('Relationships', () => {
expect(query.docs[0].id).toStrictEqual(firstLevelID)
})
it('should allow querying on id two levels deep', async () => {
const query = await payload.find({
collection: 'chained',
where: {
'relation.relation.id': {
equals: thirdLevelID,
},
},
})
expect(query.docs).toHaveLength(1)
expect(query.docs[0].id).toStrictEqual(firstLevelID)
const { result: queryREST } = await client.find({
slug: 'chained',
query: {
'relation.relation.id': {
equals: thirdLevelID,
},
},
})
expect(queryREST.docs).toHaveLength(1)
expect(queryREST.docs[0].id).toStrictEqual(firstLevelID)
})
it('should allow querying within array nesting', async () => {
const page = await payload.create({
collection: 'pages',