This makes it possible to add custom logic into how we map the document
data into the CSV data on a field-by-field basis.
- Allow custom data transformation to be added to
`custom.['plugin-import-export'].toCSV inside the field config
- Add type declaration to FieldCustom to improve types
- Export with `depth: 1`
Example:
```ts
{
name: 'customRelationship',
type: 'relationship',
relationTo: 'users',
custom: {
'plugin-import-export': {
toCSV: ({ value, columnName, row, siblingDoc, doc }) => {
row[`${columnName}_id`] = value.id
row[`${columnName}_email`] = value.email
},
},
},
},
```
572 lines
16 KiB
TypeScript
572 lines
16 KiB
TypeScript
import type { CollectionSlug, Payload } from 'payload'
|
|
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
|
|
|
import { devUser } from '../credentials.js'
|
|
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
|
import { readCSV, readJSON } from './helpers.js'
|
|
import { richTextData } from './seed/richTextData.js'
|
|
|
|
let payload: Payload
|
|
let restClient: NextRESTClient
|
|
let user: any
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
describe('@payloadcms/plugin-import-export', () => {
|
|
beforeAll(async () => {
|
|
;({ payload, restClient } = await initPayloadInt(dirname))
|
|
user = await payload.login({
|
|
collection: 'users',
|
|
data: {
|
|
email: devUser.email,
|
|
password: devUser.password,
|
|
},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await payload.destroy()
|
|
})
|
|
|
|
describe('graphql', () => {
|
|
it('should not break graphql', async () => {
|
|
const query = `query {
|
|
__schema {
|
|
queryType {
|
|
name
|
|
}
|
|
}
|
|
}`
|
|
const response = await restClient
|
|
.GRAPHQL_POST({
|
|
body: JSON.stringify({ query }),
|
|
})
|
|
.then((res) => res.json())
|
|
|
|
expect(response.error).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('exports', () => {
|
|
it('should create a file for collection csv from defined fields', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
sort: 'createdAt',
|
|
fields: ['id', 'title', 'group.value', 'group.array.field1', 'createdAt', 'updatedAt'],
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Title ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toContain('pages.csv')
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].id).toBeDefined()
|
|
expect(data[0].title).toStrictEqual('Title 0')
|
|
expect(data[0].group_value).toStrictEqual('group value')
|
|
expect(data[0].group_ignore).toBeUndefined()
|
|
expect(data[0].group_array_0_field1).toStrictEqual('test')
|
|
expect(data[0].createdAt).toBeDefined()
|
|
expect(data[0].updatedAt).toBeDefined()
|
|
})
|
|
|
|
it('should create a file for collection csv with draft data', async () => {
|
|
const draftPage = await payload.create({
|
|
collection: 'pages',
|
|
user,
|
|
data: {
|
|
title: 'Draft Page',
|
|
_status: 'published',
|
|
},
|
|
})
|
|
|
|
await payload.update({
|
|
collection: 'pages',
|
|
id: draftPage.id,
|
|
data: {
|
|
title: 'Draft Page Updated',
|
|
_status: 'draft',
|
|
},
|
|
})
|
|
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'title', '_status'],
|
|
locale: 'en',
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Draft ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].id).toBeDefined()
|
|
expect(data[0].title).toStrictEqual('Draft Page Updated')
|
|
expect(data[0]._status).toStrictEqual('draft')
|
|
})
|
|
|
|
it('should create a file for collection csv from one locale', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'localized'],
|
|
locale: 'en',
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Localized ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].id).toBeDefined()
|
|
expect(data[0].localized).toStrictEqual('en test')
|
|
})
|
|
|
|
it('should create a file for collection csv from multiple locales', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'localized'],
|
|
locale: 'all',
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Localized ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].id).toBeDefined()
|
|
expect(data[0].localized_en).toStrictEqual('en test')
|
|
expect(data[0].localized_es).toStrictEqual('es test')
|
|
})
|
|
|
|
it('should create a file for collection csv from array', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'array'],
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Array ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].array_0_field1).toStrictEqual('foo')
|
|
expect(data[0].array_0_field2).toStrictEqual('bar')
|
|
expect(data[0].array_1_field1).toStrictEqual('foo')
|
|
expect(data[0].array_1_field2).toStrictEqual('baz')
|
|
})
|
|
|
|
it('should create a CSV file with columns matching the order of the fields array', async () => {
|
|
const fields = ['id', 'group.value', 'group.array.field1', 'title', 'createdAt', 'updatedAt']
|
|
const doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields,
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Title ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
const exportDoc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(exportDoc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
|
|
const buffer = fs.readFileSync(expectedPath)
|
|
const str = buffer.toString()
|
|
|
|
// Assert that the header row matches the fields array
|
|
expect(str.indexOf('id')).toBeLessThan(str.indexOf('title'))
|
|
expect(str.indexOf('group_value')).toBeLessThan(str.indexOf('title'))
|
|
expect(str.indexOf('group_value')).toBeLessThan(str.indexOf('group_array'))
|
|
expect(str.indexOf('title')).toBeLessThan(str.indexOf('createdAt'))
|
|
expect(str.indexOf('createdAt')).toBeLessThan(str.indexOf('updatedAt'))
|
|
})
|
|
|
|
it('should create a CSV file with virtual fields', async () => {
|
|
const fields = ['id', 'virtual', 'virtualRelationship']
|
|
const doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields,
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Virtual ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
const exportDoc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(exportDoc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
// Assert that the csv file contains the expected virtual fields
|
|
expect(data[0].virtual).toStrictEqual('virtual value')
|
|
expect(data[0].virtualRelationship).toStrictEqual('name value')
|
|
})
|
|
|
|
it('should create a file for collection csv from array.subfield', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'array.field1'],
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Array Subfield ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].array_0_field1).toStrictEqual('foo')
|
|
expect(data[0].array_0_field2).toBeUndefined()
|
|
expect(data[0].array_1_field1).toStrictEqual('foo')
|
|
expect(data[0].array_1_field2).toBeUndefined()
|
|
})
|
|
|
|
it('should create a file for collection csv from hasMany field', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'hasManyNumber'],
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'hasMany Number ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].hasManyNumber_0).toStrictEqual('0')
|
|
expect(data[0].hasManyNumber_1).toStrictEqual('1')
|
|
expect(data[0].hasManyNumber_2).toStrictEqual('1')
|
|
expect(data[0].hasManyNumber_3).toStrictEqual('2')
|
|
expect(data[0].hasManyNumber_4).toStrictEqual('3')
|
|
})
|
|
|
|
it('should create a file for collection csv from blocks field', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'blocks'],
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Blocks ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].blocks_0_blockType).toStrictEqual('hero')
|
|
expect(data[0].blocks_1_blockType).toStrictEqual('content')
|
|
})
|
|
|
|
it('should run custom toCSV function on a field', async () => {
|
|
const fields = [
|
|
'id',
|
|
'custom',
|
|
'group.custom',
|
|
'customRelationship',
|
|
'tabToCSV',
|
|
'namedTab.tabToCSV',
|
|
]
|
|
const doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields,
|
|
format: 'csv',
|
|
where: {
|
|
title: { contains: 'Custom ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
const exportDoc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(exportDoc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
// Assert that the csv file contains the expected virtual fields
|
|
expect(data[0].custom).toStrictEqual('my custom csv transformer toCSV')
|
|
expect(data[0].group_custom).toStrictEqual('my custom csv transformer toCSV')
|
|
expect(data[0].tabToCSV).toStrictEqual('my custom csv transformer toCSV')
|
|
expect(data[0].namedTab_tabToCSV).toStrictEqual('my custom csv transformer toCSV')
|
|
expect(data[0].customRelationship_id).toBeDefined()
|
|
expect(data[0].customRelationship_email).toBeDefined()
|
|
expect(data[0].customRelationship_createdAt).toBeUndefined()
|
|
})
|
|
|
|
it('should create a JSON file for collection', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'title'],
|
|
format: 'json',
|
|
sort: 'title',
|
|
where: {
|
|
title: { contains: 'JSON ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readJSON(expectedPath)
|
|
|
|
expect(data[0].title).toStrictEqual('JSON 0')
|
|
})
|
|
|
|
it('should create an export with every field when no fields are defined', async () => {
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
format: 'json',
|
|
sort: 'title',
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readJSON(expectedPath)
|
|
|
|
expect(data[0].id).toBeDefined()
|
|
expect(data[0].title).toBeDefined()
|
|
expect(data[0].createdAt).toBeDefined()
|
|
expect(data[0].updatedAt).toBeDefined()
|
|
})
|
|
|
|
it('should create jobs task for exports', async () => {
|
|
const doc = await payload.create({
|
|
collection: 'exports-tasks' as CollectionSlug,
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'title'],
|
|
format: 'csv',
|
|
sort: 'title',
|
|
where: {
|
|
title: { contains: 'Jobs ' },
|
|
},
|
|
},
|
|
})
|
|
|
|
const { docs } = await payload.find({
|
|
collection: 'payload-jobs' as CollectionSlug,
|
|
})
|
|
const job = docs[0]
|
|
|
|
expect(job).toBeDefined()
|
|
|
|
const { input } = job
|
|
|
|
expect(input.id).toBeDefined()
|
|
expect(input.name).toBeDefined()
|
|
expect(input.format).toStrictEqual('csv')
|
|
expect(input.locale).toStrictEqual('all')
|
|
expect(input.fields).toStrictEqual(['id', 'title'])
|
|
expect(input.collectionSlug).toStrictEqual('pages')
|
|
expect(input.exportsCollection).toStrictEqual('exports-tasks')
|
|
expect(input.user).toBeDefined()
|
|
expect(input.userCollection).toBeDefined()
|
|
|
|
await payload.jobs.run()
|
|
|
|
const exportDoc = await payload.findByID({
|
|
collection: 'exports-tasks' as CollectionSlug,
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(exportDoc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', exportDoc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].title).toStrictEqual('Jobs 0')
|
|
})
|
|
|
|
// disabled so we don't always run a massive test
|
|
it.skip('should create a file from a large set of collection documents', async () => {
|
|
const allPromises = []
|
|
let promises = []
|
|
for (let i = 0; i < 100000; i++) {
|
|
promises.push(
|
|
payload.create({
|
|
collectionSlug: 'pages',
|
|
data: {
|
|
title: `Array ${i}`,
|
|
blocks: [
|
|
{
|
|
blockType: 'hero',
|
|
title: 'test',
|
|
},
|
|
{
|
|
blockType: 'content',
|
|
richText: richTextData,
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
)
|
|
if (promises.length >= 500) {
|
|
await Promise.all(promises)
|
|
promises = []
|
|
}
|
|
if (i % 1000 === 0) {
|
|
console.log('created', i)
|
|
}
|
|
}
|
|
await Promise.all(promises)
|
|
|
|
console.log('seeded')
|
|
|
|
let doc = await payload.create({
|
|
collection: 'exports',
|
|
user,
|
|
data: {
|
|
collectionSlug: 'pages',
|
|
fields: ['id', 'blocks'],
|
|
format: 'csv',
|
|
},
|
|
})
|
|
|
|
doc = await payload.findByID({
|
|
collection: 'exports',
|
|
id: doc.id,
|
|
})
|
|
|
|
expect(doc.filename).toBeDefined()
|
|
const expectedPath = path.join(dirname, './uploads', doc.filename as string)
|
|
const data = await readCSV(expectedPath)
|
|
|
|
expect(data[0].blocks_0_blockType).toStrictEqual('hero')
|
|
expect(data[0].blocks_1_blockType).toStrictEqual('content')
|
|
})
|
|
})
|
|
})
|