feat(plugin-import-export): adds support for disabling fields (#13166)
### What? Adds support for excluding specific fields from the import-export plugin using a custom field config. ### Why? Some fields should not be included in exports or previews. This feature allows users to flag those fields directly in the field config. ### How? - Introduced a `plugin-import-export.disabled: true` custom field property. - Automatically collects and stores disabled field accessors in `collection.admin.custom['plugin-import-export'].disabledFields`. - Excludes these fields from the export field selector, preview table, and final export output (CSV/JSON).
This commit is contained in:
@@ -27,7 +27,14 @@ export const FieldsToExport: SelectFieldClientComponent = (props) => {
|
||||
const { query } = useListQuery()
|
||||
|
||||
const collectionConfig = getEntityConfig({ collectionSlug: collectionSlug ?? collection })
|
||||
const fieldOptions = reduceFields({ fields: collectionConfig?.fields })
|
||||
|
||||
const disabledFields =
|
||||
collectionConfig?.admin?.custom?.['plugin-import-export']?.disabledFields ?? []
|
||||
|
||||
const fieldOptions = reduceFields({
|
||||
disabledFields,
|
||||
fields: collectionConfig?.fields,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (id || !collectionSlug) {
|
||||
|
||||
@@ -43,10 +43,12 @@ const combineLabel = ({
|
||||
}
|
||||
|
||||
export const reduceFields = ({
|
||||
disabledFields = [],
|
||||
fields,
|
||||
labelPrefix = null,
|
||||
path = '',
|
||||
}: {
|
||||
disabledFields?: string[]
|
||||
fields: ClientField[]
|
||||
labelPrefix?: React.ReactNode
|
||||
path?: string
|
||||
@@ -66,6 +68,7 @@ export const reduceFields = ({
|
||||
return [
|
||||
...fieldsToUse,
|
||||
...reduceFields({
|
||||
disabledFields,
|
||||
fields: field.fields,
|
||||
labelPrefix: combineLabel({ field, prefix: labelPrefix }),
|
||||
path: createNestedClientFieldPath(path, field),
|
||||
@@ -83,6 +86,7 @@ export const reduceFields = ({
|
||||
return [
|
||||
...tabFields,
|
||||
...reduceFields({
|
||||
disabledFields,
|
||||
fields: tab.fields,
|
||||
labelPrefix,
|
||||
path: isNamedTab ? createNestedClientFieldPath(path, field) : path,
|
||||
@@ -98,6 +102,11 @@ export const reduceFields = ({
|
||||
|
||||
const val = createNestedClientFieldPath(path, field)
|
||||
|
||||
// If the field is disabled, skip it
|
||||
if (disabledFields.includes(val)) {
|
||||
return fieldsToUse
|
||||
}
|
||||
|
||||
const formattedField = {
|
||||
id: val,
|
||||
label: combineLabel({ field, prefix: labelPrefix }),
|
||||
|
||||
@@ -46,6 +46,14 @@ export const Preview = () => {
|
||||
(collection) => collection.slug === collectionSlug,
|
||||
)
|
||||
|
||||
const disabledFieldsUnderscored = React.useMemo(() => {
|
||||
return (
|
||||
collectionConfig?.admin?.custom?.['plugin-import-export']?.disabledFields?.map((f: string) =>
|
||||
f.replace(/\./g, '_'),
|
||||
) ?? []
|
||||
)
|
||||
}, [collectionConfig])
|
||||
|
||||
const isCSV = format === 'csv'
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -95,7 +103,10 @@ export const Preview = () => {
|
||||
const regex = fieldToRegex(field)
|
||||
return allKeys.filter((key) => regex.test(key))
|
||||
})
|
||||
: allKeys.filter((key) => !defaultMetaFields.includes(key))
|
||||
: allKeys.filter(
|
||||
(key) =>
|
||||
!defaultMetaFields.includes(key) && !disabledFieldsUnderscored.includes(key),
|
||||
)
|
||||
|
||||
const fieldKeys =
|
||||
Array.isArray(fields) && fields.length > 0
|
||||
@@ -136,7 +147,18 @@ export const Preview = () => {
|
||||
}
|
||||
|
||||
void fetchData()
|
||||
}, [collectionConfig, collectionSlug, draft, fields, i18n, limit, locale, sort, where])
|
||||
}, [
|
||||
collectionConfig,
|
||||
collectionSlug,
|
||||
disabledFieldsUnderscored,
|
||||
draft,
|
||||
fields,
|
||||
i18n,
|
||||
limit,
|
||||
locale,
|
||||
sort,
|
||||
where,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
|
||||
@@ -108,6 +108,17 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
fields: collectionConfig.flattenedFields,
|
||||
})
|
||||
|
||||
const disabledFieldsDot =
|
||||
collectionConfig.admin?.custom?.['plugin-import-export']?.disabledFields ?? []
|
||||
const disabledFields = disabledFieldsDot.map((f: string) => f.replace(/\./g, '_'))
|
||||
|
||||
const filterDisabled = (row: Record<string, unknown>): Record<string, unknown> => {
|
||||
for (const key of disabledFields) {
|
||||
delete row[key]
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
if (download) {
|
||||
if (debug) {
|
||||
req.payload.logger.info('Pre-scanning all columns before streaming')
|
||||
@@ -122,7 +133,7 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
const result = await payload.find({ ...findArgs, page: scanPage })
|
||||
|
||||
result.docs.forEach((doc) => {
|
||||
const flat = flattenObject({ doc, fields, toCSVFunctions })
|
||||
const flat = filterDisabled(flattenObject({ doc, fields, toCSVFunctions }))
|
||||
Object.keys(flat).forEach((key) => {
|
||||
if (!allColumnsSet.has(key)) {
|
||||
allColumnsSet.add(key)
|
||||
@@ -156,7 +167,9 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
return
|
||||
}
|
||||
|
||||
const batchRows = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
|
||||
const batchRows = result.docs.map((doc) =>
|
||||
filterDisabled(flattenObject({ doc, fields, toCSVFunctions })),
|
||||
)
|
||||
|
||||
const paddedRows = batchRows.map((row) => {
|
||||
const fullRow: Record<string, unknown> = {}
|
||||
@@ -217,7 +230,9 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
}
|
||||
|
||||
if (isCSV) {
|
||||
const batchRows = result.docs.map((doc) => flattenObject({ doc, fields, toCSVFunctions }))
|
||||
const batchRows = result.docs.map((doc) =>
|
||||
filterDisabled(flattenObject({ doc, fields, toCSVFunctions })),
|
||||
)
|
||||
|
||||
// Track discovered column keys
|
||||
batchRows.forEach((row) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Config, FlattenedField } from 'payload'
|
||||
|
||||
import { addDataAndFileToRequest, deepMergeSimple } from 'payload'
|
||||
import { addDataAndFileToRequest, deepMergeSimple, flattenTopLevelFields } from 'payload'
|
||||
|
||||
import type { PluginDefaultTranslationsObject } from './translations/types.js'
|
||||
import type { ImportExportPluginConfig, ToCSVFunction } from './types.js'
|
||||
@@ -58,6 +58,26 @@ export const importExportPlugin =
|
||||
},
|
||||
path: '@payloadcms/plugin-import-export/rsc#ExportListMenuItem',
|
||||
})
|
||||
|
||||
// Flatten top-level fields to expose nested fields for export config
|
||||
const flattenedFields = flattenTopLevelFields(collection.fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
})
|
||||
|
||||
// Find fields explicitly marked as disabled for import/export
|
||||
const disabledFieldAccessors = flattenedFields
|
||||
.filter((field) => field.custom?.['plugin-import-export']?.disabled)
|
||||
.map((field) => field.accessor || field.name)
|
||||
|
||||
// Store disabled field accessors in the admin config for use in the UI
|
||||
collection.admin.custom = {
|
||||
...(collection.admin.custom || {}),
|
||||
'plugin-import-export': {
|
||||
...(collection.admin.custom?.['plugin-import-export'] || {}),
|
||||
disabledFields: disabledFieldAccessors,
|
||||
},
|
||||
}
|
||||
|
||||
collection.admin.components = components
|
||||
})
|
||||
|
||||
@@ -161,6 +181,14 @@ export const importExportPlugin =
|
||||
declare module 'payload' {
|
||||
export interface FieldCustom {
|
||||
'plugin-import-export'?: {
|
||||
/**
|
||||
* When `true` the field is **completely excluded** from the import-export plugin:
|
||||
* - It will not appear in the “Fields to export” selector.
|
||||
* - It is hidden from the preview list when no specific fields are chosen.
|
||||
* - Its data is omitted from the final CSV / JSON export.
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean
|
||||
toCSV?: ToCSVFunction
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user