fix(ui): exclude fields lacking permissions from bulk edit (#11776)
Top-level fields that lack read or update permissions still appear as options in the field selector within the bulk edit drawer.
This commit is contained in:
@@ -65,6 +65,20 @@ export const PostsCollection: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'noRead',
|
||||
type: 'text',
|
||||
access: {
|
||||
read: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'noUpdate',
|
||||
type: 'text',
|
||||
access: {
|
||||
update: () => false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -82,5 +96,19 @@ export const PostsCollection: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'noRead',
|
||||
type: 'text',
|
||||
access: {
|
||||
read: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'noUpdate',
|
||||
type: 'text',
|
||||
access: {
|
||||
update: () => false,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BrowserContext, Page } from '@playwright/test'
|
||||
import type { BrowserContext, Locator, Page } from '@playwright/test'
|
||||
import type { PayloadTestSDK } from 'helpers/sdk/index.js'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
@@ -176,18 +176,14 @@ test.describe('Bulk Edit', () => {
|
||||
|
||||
await page.locator('input#select-all').check()
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
await page.locator('.field-select .rs__control').click()
|
||||
|
||||
const titleOption = page.locator('.field-select .rs__option', {
|
||||
hasText: exactText('Title'),
|
||||
const { field, modal } = await selectFieldToEdit(page, {
|
||||
fieldLabel: 'Title',
|
||||
fieldID: 'title',
|
||||
})
|
||||
|
||||
await expect(titleOption).toBeVisible()
|
||||
await titleOption.click()
|
||||
const titleInput = page.locator('#field-title')
|
||||
await expect(titleInput).toBeVisible()
|
||||
await titleInput.fill(updatedPostTitle)
|
||||
await page.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||
await field.fill(updatedPostTitle)
|
||||
await modal.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
|
||||
'Updated 3 Posts successfully.',
|
||||
@@ -222,14 +218,17 @@ test.describe('Bulk Edit', () => {
|
||||
await selectTableRow(page, titleOfPostToPublish1)
|
||||
await selectTableRow(page, titleOfPostToPublish2)
|
||||
|
||||
// Bulk edit the selected rows to `published` status
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
await page.locator('.field-select .rs__control').click()
|
||||
const options = page.locator('.rs__option')
|
||||
const field = options.locator('text=Description')
|
||||
await field.click()
|
||||
await page.locator('#field-description').fill(description)
|
||||
await page.locator('.form-submit .edit-many__publish').click()
|
||||
|
||||
const { field, modal } = await selectFieldToEdit(page, {
|
||||
fieldLabel: 'Description',
|
||||
fieldID: 'description',
|
||||
})
|
||||
|
||||
await field.fill(description)
|
||||
|
||||
// Bulk edit the selected rows to `published` status
|
||||
await modal.locator('.form-submit .edit-many__publish').click()
|
||||
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
|
||||
'Updated 2 Posts successfully.',
|
||||
@@ -258,12 +257,16 @@ test.describe('Bulk Edit', () => {
|
||||
await selectTableRow(page, titleOfPostToDraft2)
|
||||
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
await page.locator('.field-select .rs__control').click()
|
||||
const options = page.locator('.rs__option')
|
||||
const field = options.locator('text=Description')
|
||||
await field.click()
|
||||
await page.locator('#field-description').fill(description)
|
||||
await page.locator('.form-submit .edit-many__draft').click()
|
||||
|
||||
const { field, modal } = await selectFieldToEdit(page, {
|
||||
fieldLabel: 'Description',
|
||||
fieldID: 'description',
|
||||
})
|
||||
|
||||
await field.fill(description)
|
||||
|
||||
// Bulk edit the selected rows to `draft` status
|
||||
await modal.locator('.form-submit .edit-many__draft').click()
|
||||
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
|
||||
'Updated 2 Posts successfully.',
|
||||
@@ -336,18 +339,14 @@ test.describe('Bulk Edit', () => {
|
||||
await page.locator('button#select-all-across-pages').click()
|
||||
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
await page.locator('.field-select .rs__control').click()
|
||||
|
||||
const titleOption = page.locator('.field-select .rs__option', {
|
||||
hasText: exactText('Title'),
|
||||
const { field } = await selectFieldToEdit(page, {
|
||||
fieldLabel: 'Title',
|
||||
fieldID: 'title',
|
||||
})
|
||||
|
||||
await expect(titleOption).toBeVisible()
|
||||
await titleOption.click()
|
||||
const titleInput = page.locator('#field-title')
|
||||
await expect(titleInput).toBeVisible()
|
||||
const updatedTitle = `Post (Updated)`
|
||||
await titleInput.fill(updatedTitle)
|
||||
const updatedTitle = 'Post (Updated)'
|
||||
await field.fill(updatedTitle)
|
||||
|
||||
await page.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
|
||||
@@ -389,19 +388,18 @@ test.describe('Bulk Edit', () => {
|
||||
const updatedPostTitle = 'Post 1 (Updated)'
|
||||
|
||||
const { id: postID } = await createPost(postData)
|
||||
await page.goto(postsUrl.list)
|
||||
await page.locator('input#select-all').check()
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
await page.locator('.field-select .rs__control').click()
|
||||
|
||||
const titleOption = page.locator('.field-select .rs__option', {
|
||||
hasText: exactText('Title'),
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
const { modal } = await selectAllAndEditMany(page)
|
||||
|
||||
const { field } = await selectFieldToEdit(page, {
|
||||
fieldLabel: 'Title',
|
||||
fieldID: 'title',
|
||||
})
|
||||
|
||||
await titleOption.click()
|
||||
const titleInput = page.locator('#field-title')
|
||||
await titleInput.fill(updatedPostTitle)
|
||||
await page.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||
await field.fill(updatedPostTitle)
|
||||
await modal.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
|
||||
'Updated 1 Post successfully.',
|
||||
@@ -427,23 +425,19 @@ test.describe('Bulk Edit', () => {
|
||||
test('should bulk edit fields with subfields', async () => {
|
||||
await deleteAllPosts()
|
||||
|
||||
const { id: docID } = await createPost()
|
||||
await createPost()
|
||||
|
||||
await page.goto(postsUrl.list)
|
||||
await page.locator('input#select-all').check()
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
await page.locator('.field-select .rs__control').click()
|
||||
|
||||
const bulkEditModal = page.locator('#edit-posts')
|
||||
await selectAllAndEditMany(page)
|
||||
|
||||
const titleOption = bulkEditModal.locator('.field-select .rs__option', {
|
||||
hasText: exactText('Group > Title'),
|
||||
const { modal, field } = await selectFieldToEdit(page, {
|
||||
fieldLabel: 'Group > Title',
|
||||
fieldID: 'group__title',
|
||||
})
|
||||
|
||||
await titleOption.click()
|
||||
const titleInput = bulkEditModal.locator('#field-group__title')
|
||||
await titleInput.fill('New Group Title')
|
||||
await page.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||
await field.fill('New Group Title')
|
||||
await modal.locator('.form-submit button[type="submit"].edit-many__publish').click()
|
||||
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
|
||||
'Updated 1 Post successfully.',
|
||||
@@ -458,8 +452,81 @@ test.describe('Bulk Edit', () => {
|
||||
|
||||
expect(updatedPost?.group?.title).toBe('New Group Title')
|
||||
})
|
||||
|
||||
test('should not display fields options lacking read and update permissions', async () => {
|
||||
await deleteAllPosts()
|
||||
|
||||
await createPost()
|
||||
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
const { modal } = await selectAllAndEditMany(page)
|
||||
|
||||
await expect(
|
||||
modal.locator('.field-select .rs__option', { hasText: exactText('No Read') }),
|
||||
).toBeHidden()
|
||||
|
||||
await expect(
|
||||
modal.locator('.field-select .rs__option', { hasText: exactText('No Update') }),
|
||||
).toBeHidden()
|
||||
})
|
||||
|
||||
test('should thread field permissions through subfields', async () => {
|
||||
await deleteAllPosts()
|
||||
|
||||
await createPost()
|
||||
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await selectAllAndEditMany(page)
|
||||
|
||||
const { field } = await selectFieldToEdit(page, { fieldLabel: 'Array', fieldID: 'array' })
|
||||
|
||||
await field.locator('button.array-field__add-row').click()
|
||||
|
||||
await expect(field.locator('#field-array__0__optional')).toBeVisible()
|
||||
await expect(field.locator('#field-array__0__noRead')).toBeHidden()
|
||||
await expect(field.locator('#field-array__0__noUpdate')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
async function selectFieldToEdit(
|
||||
page: Page,
|
||||
{
|
||||
fieldLabel,
|
||||
fieldID,
|
||||
}: {
|
||||
fieldID: string
|
||||
fieldLabel: string
|
||||
},
|
||||
): Promise<{ field: Locator; modal: Locator }> {
|
||||
// ensure modal is open, open if needed
|
||||
const isModalOpen = await page.locator('#edit-posts').isVisible()
|
||||
|
||||
if (!isModalOpen) {
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
}
|
||||
|
||||
const modal = page.locator('#edit-posts')
|
||||
await expect(modal).toBeVisible()
|
||||
|
||||
await modal.locator('.field-select .rs__control').click()
|
||||
await modal.locator('.field-select .rs__option', { hasText: exactText(fieldLabel) }).click()
|
||||
|
||||
const field = modal.locator(`#field-${fieldID}`)
|
||||
await expect(field).toBeVisible()
|
||||
|
||||
return { modal, field }
|
||||
}
|
||||
|
||||
async function selectAllAndEditMany(page: Page): Promise<{ modal: Locator }> {
|
||||
await page.locator('input#select-all').check()
|
||||
await page.locator('.edit-many__toggle').click()
|
||||
const modal = page.locator('#edit-posts')
|
||||
await expect(modal).toBeVisible()
|
||||
return { modal }
|
||||
}
|
||||
|
||||
async function deleteAllPosts() {
|
||||
await payload.delete({ collection: postsSlug, where: { id: { exists: true } } })
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export interface Config {
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {};
|
||||
globalsSelect: {};
|
||||
@@ -118,7 +118,7 @@ export interface UserAuthOperations {
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
id: string;
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
defaultValueField?: string | null;
|
||||
@@ -135,6 +135,8 @@ export interface Post {
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
noRead?: string | null;
|
||||
noUpdate?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
@@ -146,6 +148,8 @@ export interface Post {
|
||||
blockType: 'textBlock';
|
||||
}[]
|
||||
| null;
|
||||
noRead?: string | null;
|
||||
noUpdate?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
@@ -155,7 +159,7 @@ export interface Post {
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
@@ -172,20 +176,20 @@ export interface User {
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: number;
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -195,10 +199,10 @@ export interface PayloadLockedDocument {
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: number;
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
@@ -218,7 +222,7 @@ export interface PayloadPreference {
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: number;
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
@@ -248,6 +252,8 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
innerOptional?: T;
|
||||
id?: T;
|
||||
};
|
||||
noRead?: T;
|
||||
noUpdate?: T;
|
||||
id?: T;
|
||||
};
|
||||
blocks?:
|
||||
@@ -261,6 +267,8 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
noRead?: T;
|
||||
noUpdate?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
|
||||
Reference in New Issue
Block a user