fix(content-manager): documentId missing from fields for the view and… (#23604)

* fix(content-manager): documentId missing from fields for the view and filters

---------

Co-authored-by: Mark Kaylor <mark.kaylor@strapi.io>
This commit is contained in:
Adrien L 2025-06-10 17:43:39 +02:00 committed by GitHub
parent 3561ab6db9
commit df5dd4b92e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 131 additions and 90 deletions

View File

@ -169,6 +169,7 @@ describe('useDocument', () => {
},
attributes: {
id: { type: 'string' },
documentId: { type: 'string' },
slug: { type: 'uid' },
name: { type: 'string' },
city: {
@ -293,6 +294,9 @@ describe('useDocument', () => {
id: {
type: 'string',
},
documentId: {
type: 'string',
},
name: {
type: 'string',
default: 'toto',
@ -338,6 +342,7 @@ describe('useDocument', () => {
expect(result.current.getInitialFormValues).toBeInstanceOf(Function);
expect(result.current.getInitialFormValues()).toEqual({
documentId: '12345',
name: 'Entry 1',
});
});

View File

@ -39,7 +39,6 @@ describe('useDocumentActions', () => {
documentId: '12345',
},
{
documentId: '12345',
title: 'test',
content: 'the brown fox jumps over the lazy dog',
}
@ -51,7 +50,7 @@ describe('useDocumentActions', () => {
expect(response).toEqual({
data: {
content: 'the brown fox jumps over the lazy dog',
documentId: '12345',
documentId: '67890',
id: 2,
title: 'test',
},

View File

@ -681,7 +681,8 @@ const useDocumentActions: UseDocumentActions = () => {
const clone: IUseDocumentActs['clone'] = React.useCallback(
async ({ model, documentId, params }, body, trackerProperty) => {
try {
const { id: _id, ...restBody } = body;
// Omit id and documentId so they are not copied to the clone
const { id: _id, documentId: _documentId, ...restBody } = body;
/**
* If we're cloning we want to post directly to this endpoint

View File

@ -1,6 +1,11 @@
import { testData } from '../../../../tests/data';
import { removeProhibitedFields } from '../data';
const defaultFieldsValues = {
name: 'name',
password: '',
};
describe('data', () => {
describe('removeProhibitedFields', () => {
it('should return an empty object', () => {
@ -13,9 +18,9 @@ describe('data', () => {
const { components, contentType } = testData;
expect(
removeProhibitedFields(['password'])(contentType, components)({ name: 'test' })
removeProhibitedFields(['password'])(contentType, components)({ name: 'name' })
).toEqual({
name: 'test',
name: 'name',
});
});
@ -24,10 +29,10 @@ describe('data', () => {
expect(
removeProhibitedFields(['password'])(contentType, components)({
name: 'test',
name: 'name',
password: 'password',
})
).toEqual({ name: 'test', password: '' });
).toEqual(defaultFieldsValues);
});
it('should remove all password fields', () => {
@ -36,106 +41,70 @@ describe('data', () => {
const result = removeProhibitedFields(['password'])(contentType, components)(modifiedData);
expect(result).toEqual({
id: 1,
name: 'name',
createdAt: '2020-04-28T13:22:13.033Z',
updatedAt: '2020-04-28T13:22:13.033Z',
password: '',
notrepeatable: {
id: 1,
name: 'name',
password: '',
subcomponotrepeatable: {
id: 4,
name: 'name',
password: '',
},
subrepeatable: [
{
id: 1,
name: 'name',
password: '',
},
{
id: 2,
name: 'name',
password: '',
},
{
id: 3,
name: 'name',
password: '',
},
],
},
repeatable: [
{
id: 2,
name: 'name',
password: '',
subcomponotrepeatable: {
id: 6,
name: 'name',
password: '',
},
subrepeatable: [
{
id: 5,
name: 'name',
password: '',
},
],
},
{
id: 3,
name: 'name',
password: '',
subcomponotrepeatable: null,
subrepeatable: [],
},
],
dz: [
{
__component: 'compos.sub-compo',
id: 7,
name: 'name',
password: '',
...defaultFieldsValues,
},
{
__component: 'compos.test-compo',
id: 4,
name: 'name',
password: '',
documentId: '456789',
...defaultFieldsValues,
subcomponotrepeatable: null,
subrepeatable: [],
__component: 'compos.test-compo',
},
{
__component: 'compos.test-compo',
id: 5,
name: 'name',
password: '',
subcomponotrepeatable: {
id: 9,
name: 'name',
password: '',
},
subrepeatable: [
{
id: 8,
name: 'name',
password: '',
},
],
documentId: '567890',
...defaultFieldsValues,
subcomponotrepeatable: { id: 9, name: 'name', password: '' },
subrepeatable: [{ id: 8, name: 'name', password: '' }],
__component: 'compos.test-compo',
},
{
__component: 'compos.test-compo',
id: 6,
documentId: '678901',
name: null,
password: null,
subcomponotrepeatable: null,
subrepeatable: [],
__component: 'compos.test-compo',
},
],
id: 1,
name: 'name',
notrepeatable: {
id: 1,
documentId: '123456',
...defaultFieldsValues,
subcomponotrepeatable: { id: 4, name: 'name', password: '' },
subrepeatable: [
{ id: 1, name: 'name', password: '' },
{ id: 2, name: 'name', password: '' },
{ id: 3, name: 'name', password: '' },
],
},
password: '',
repeatable: [
{
id: 2,
documentId: '234567',
...defaultFieldsValues,
subrepeatable: [{ id: 5, name: 'name', password: '' }],
subcomponotrepeatable: { id: 6, name: 'name', password: '' },
},
{
id: 3,
documentId: '345678',
...defaultFieldsValues,
subrepeatable: [],
subcomponotrepeatable: null,
},
],
updatedAt: '2020-04-28T13:22:13.033Z',
});
});
});

View File

@ -108,6 +108,7 @@ const FiltersImpl = ({ disabled, schema }: FiltersProps) => {
return (
[
'id',
'documentId',
...allowedFields,
...DEFAULT_ALLOWED_FILTERS,
...(canReadAdminUsers ? CREATOR_FIELDS : []),

View File

@ -20,6 +20,7 @@ const testData = {
createdAt: { type: 'timestamp' },
dz: { type: 'dynamiczone', components: ['compos.test-compo', 'compos.sub-compo'] },
id: { type: 'integer' },
documentId: { type: 'string' },
name: { type: 'string' },
notrepeatable: {
type: 'component',
@ -45,6 +46,7 @@ const testData = {
},
attributes: {
id: { type: 'integer' },
documentId: { type: 'string' },
name: { type: 'string' },
password: { type: 'password' },
},
@ -62,6 +64,7 @@ const testData = {
},
attributes: {
id: { type: 'integer' },
documentId: { type: 'string' },
name: { type: 'string' },
password: { type: 'password' },
subcomponotrepeatable: {
@ -83,6 +86,7 @@ const testData = {
{ __component: 'compos.sub-compo', id: 7, name: 'name', password: 'password' },
{
id: 4,
documentId: '456789',
name: 'name',
password: 'password',
subcomponotrepeatable: null,
@ -91,6 +95,7 @@ const testData = {
},
{
id: 5,
documentId: '567890',
name: 'name',
password: 'password',
subcomponotrepeatable: { id: 9, name: 'name', password: 'password' },
@ -99,6 +104,7 @@ const testData = {
},
{
id: 6,
documentId: '678901',
name: null,
password: null,
subcomponotrepeatable: null,
@ -110,6 +116,7 @@ const testData = {
name: 'name',
notrepeatable: {
id: 1,
documentId: '123456',
name: 'name',
password: 'password',
subcomponotrepeatable: { id: 4, name: 'name', password: 'password' },
@ -123,6 +130,7 @@ const testData = {
repeatable: [
{
id: 2,
documentId: '234567',
name: 'name',
password: 'password',
subrepeatable: [{ id: 5, name: 'name', password: 'password' }],
@ -130,6 +138,7 @@ const testData = {
},
{
id: 3,
documentId: '345678',
name: 'name',
password: 'password',
subrepeatable: [],

View File

@ -18,6 +18,9 @@ const CM_COMPONENTS_MOCK_DATA = [
id: {
type: 'string',
},
documentId: {
type: 'string',
},
name: {
type: 'string',
default: 'toto',
@ -371,6 +374,9 @@ const CM_CONTENT_TYPE_MOCK_DATA = [
id: {
type: 'string',
},
documentId: {
type: 'string',
},
name: {
type: 'string',
},
@ -488,6 +494,9 @@ const CM_CONTENT_TYPE_MOCK_DATA = [
id: {
type: 'string',
},
documentId: {
type: 'string',
},
Title: {
type: 'string',
default: 'New article',
@ -695,6 +704,14 @@ const CM_COLLECTION_TYPE_LAYOUT_MOCK_DATA = {
sortable: true,
},
},
documentId: {
edit: {},
list: {
label: 'documentId',
searchable: true,
sortable: true,
},
},
name: {
edit: {
label: 'name',

View File

@ -56,7 +56,17 @@ export default {
const confWithUpdatedMetadata = {
...configuration,
metadatas: mapValues(assocMainField, configuration.metadatas),
metadatas: {
...mapValues(assocMainField, configuration.metadatas),
documentId: {
edit: {},
list: {
label: 'documentId',
searchable: true,
sortable: true,
},
},
},
};
const components = await contentTypeService.findComponentsConfigurations(contentType);

View File

@ -27,6 +27,9 @@ export default () => ({
type: 'integer',
},
...formatAttributes(contentType),
documentId: {
type: 'string',
},
},
};
},

View File

@ -108,8 +108,10 @@ const documentManager = ({ strapi }: { strapi: Core.Strapi }) => {
uid: UID.CollectionType
) {
const populate = await buildDeepPopulate(uid);
const params = {
data: omitIdField(body),
// Ensure id and documentId are not copied to the clone
data: omit(['id', 'documentId'], body),
populate,
};

View File

@ -17,6 +17,7 @@ export interface DocumentVersion {
const AVAILABLE_STATUS_FIELDS = [
'id',
'documentId',
'locale',
'updatedAt',
'createdAt',
@ -27,6 +28,7 @@ const AVAILABLE_STATUS_FIELDS = [
];
const AVAILABLE_LOCALES_FIELDS = [
'id',
'documentId',
'locale',
'updatedAt',
'createdAt',

View File

@ -79,7 +79,7 @@ const isVisible = (schema: any, name: any) => {
return false;
}
if (isTimestamp(schema, name) || name === 'id') {
if (isTimestamp(schema, name) || name === 'id' || name === 'documentId') {
return false;
}

View File

@ -38,7 +38,7 @@ async function createDefaultLayouts(schema: any) {
function createDefaultListLayout(schema: any) {
return Object.keys(schema.attributes)
.filter((name) => isListable(schema, name))
.filter((name) => isListable(schema, name) && name !== 'documentId')
.slice(0, DEFAULT_LIST_LENGTH);
}

View File

@ -23,6 +23,14 @@ function createDefaultMetadatas(schema: any) {
sortable: true,
},
},
documentId: {
edit: {},
list: {
label: 'documentId',
searchable: true,
sortable: true,
},
},
};
}
@ -95,6 +103,7 @@ async function syncMetadatas(configuration: any, schema: any) {
const attr = schema.attributes[key];
const updatedMeta = { edit, list };
// update sortable attr
if (list.sortable && !isSortable(schema, key)) {
_.set(updatedMeta, ['list', 'sortable'], false);

View File

@ -9,11 +9,25 @@ test.describe('List View', () => {
await login({ page });
});
test('A user can filter entries', async ({ page }) => {
await page.getByRole('link', { name: 'Content Manager' }).click();
await page.getByRole('link', { name: 'Article' }).click();
await page.getByRole('button', { name: 'Filters' }).click();
await page.getByRole('combobox', { name: 'Select field' }).click();
await page.getByRole('option', { name: 'documentId' }).click();
// va0x2nt206hluydibmsoiquc => documentId for article "Why I prefer football over soccer"
await page.getByRole('textbox', { name: 'documentId' }).fill('va0x2nt206hluydibmsoiquc');
await page.getByRole('button', { name: 'Add filter' }).click();
await expect(page.getByText('documentId is va0x2nt206hluydibmsoiquc')).toBeVisible();
// There should be 2 rows, 1 for the header and 1 for the Article entry
await expect(page.getByRole('row')).toHaveCount(2);
});
test('A user should be able to navigate to the ListView of the content manager and see some entries', async ({
page,
}) => {
await page.getByRole('link', { name: 'Content Manager' }).click();
await expect(page).toHaveTitle('Article | Strapi');
await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible();
await expect(page.getByRole('link', { name: /Create new entry/ }).first()).toBeVisible();