feat(live-preview): batches api requests (#4315)

This commit is contained in:
Jacob Fletcher
2023-11-29 14:03:05 -05:00
committed by GitHub
parent 542096361f
commit d49bb4351f
6 changed files with 107 additions and 139 deletions

View File

@@ -1,5 +1,8 @@
import type { PaginatedDocs } from 'payload/database'
import type { fieldSchemaToJSON } from 'payload/utilities' import type { fieldSchemaToJSON } from 'payload/utilities'
import type { PopulationsByCollection } from './types'
import { traverseFields } from './traverseFields' import { traverseFields } from './traverseFields'
export const mergeData = async <T>(args: { export const mergeData = async <T>(args: {
@@ -27,22 +30,51 @@ export const mergeData = async <T>(args: {
const result = { ...initialData } const result = { ...initialData }
const populationPromises: Promise<void>[] = [] const populationsByCollection: PopulationsByCollection = {}
traverseFields({ traverseFields({
apiRoute,
depth,
fieldSchema, fieldSchema,
incomingData, incomingData,
populationPromises, populationsByCollection,
result, result,
serverURL,
}) })
await Promise.all(populationPromises) await Promise.all(
Object.entries(populationsByCollection).map(async ([collection, populations]) => {
const ids = new Set(populations.map(({ id }) => id))
const url = `${serverURL}${
apiRoute || '/api'
}/${collection}?depth=${depth}&where[id][in]=${Array.from(ids).join(',')}`
let res: PaginatedDocs
try {
res = await fetch(url, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res.json())
if (res?.docs?.length > 0) {
res.docs.forEach((doc) => {
populationsByCollection[collection].forEach((population) => {
if (population.id === doc.id) {
population.ref[population.accessor] = doc
}
})
})
}
} catch (err) {
console.error(err) // eslint-disable-line no-console
}
}),
)
return { return {
...result, ...result,
...(returnNumberOfRequests ? { _numberOfRequests: populationPromises.length } : {}), ...(returnNumberOfRequests
? { _numberOfRequests: Object.keys(populationsByCollection).length }
: {}),
} }
} }

View File

@@ -1,28 +0,0 @@
export const promise = async (args: {
accessor: number | string
apiRoute?: string
collection: string
depth: number
id: number | string
ref: Record<string, unknown>
serverURL: string
}): Promise<void> => {
const { id, accessor, apiRoute, collection, depth, ref, serverURL } = args
const url = `${serverURL}${apiRoute || '/api'}/${collection}/${id}?depth=${depth}`
let res: Record<string, unknown> | null | undefined = null
try {
res = await fetch(url, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res.json())
} catch (err) {
console.error(err) // eslint-disable-line no-console
}
ref[accessor] = res
}

View File

@@ -1,26 +1,16 @@
import type { fieldSchemaToJSON } from 'payload/utilities' import type { fieldSchemaToJSON } from 'payload/utilities'
import { promise } from './promise' import type { PopulationsByCollection } from './types'
import { traverseRichText } from './traverseRichText' import { traverseRichText } from './traverseRichText'
export const traverseFields = <T>(args: { export const traverseFields = <T>(args: {
apiRoute?: string
depth?: number
fieldSchema: ReturnType<typeof fieldSchemaToJSON> fieldSchema: ReturnType<typeof fieldSchemaToJSON>
incomingData: T incomingData: T
populationPromises: Promise<void>[] populationsByCollection: PopulationsByCollection
result: T result: T
serverURL: string
}): void => { }): void => {
const { const { fieldSchema: fieldSchemas, incomingData, populationsByCollection, result } = args
apiRoute,
depth,
fieldSchema: fieldSchemas,
incomingData,
populationPromises,
result,
serverURL,
} = args
fieldSchemas.forEach((fieldSchema) => { fieldSchemas.forEach((fieldSchema) => {
if ('name' in fieldSchema && typeof fieldSchema.name === 'string') { if ('name' in fieldSchema && typeof fieldSchema.name === 'string') {
@@ -29,12 +19,9 @@ export const traverseFields = <T>(args: {
switch (fieldSchema.type) { switch (fieldSchema.type) {
case 'richText': case 'richText':
result[fieldName] = traverseRichText({ result[fieldName] = traverseRichText({
apiRoute,
depth,
incomingData: incomingData[fieldName], incomingData: incomingData[fieldName],
populationPromises, populationsByCollection,
result: result[fieldName], result: result[fieldName],
serverURL,
}) })
break break
@@ -51,13 +38,10 @@ export const traverseFields = <T>(args: {
} }
traverseFields({ traverseFields({
apiRoute,
depth,
fieldSchema: fieldSchema.fields, fieldSchema: fieldSchema.fields,
incomingData: incomingRow, incomingData: incomingRow,
populationPromises, populationsByCollection,
result: result[fieldName][i], result: result[fieldName][i],
serverURL,
}) })
return result[fieldName][i] return result[fieldName][i]
@@ -86,13 +70,10 @@ export const traverseFields = <T>(args: {
} }
traverseFields({ traverseFields({
apiRoute,
depth,
fieldSchema: incomingBlockJSON.fields, fieldSchema: incomingBlockJSON.fields,
incomingData: incomingBlock, incomingData: incomingBlock,
populationPromises, populationsByCollection,
result: result[fieldName][i], result: result[fieldName][i],
serverURL,
}) })
return result[fieldName][i] return result[fieldName][i]
@@ -110,13 +91,10 @@ export const traverseFields = <T>(args: {
} }
traverseFields({ traverseFields({
apiRoute,
depth,
fieldSchema: fieldSchema.fields, fieldSchema: fieldSchema.fields,
incomingData: incomingData[fieldName] || {}, incomingData: incomingData[fieldName] || {},
populationPromises, populationsByCollection,
result: result[fieldName], result: result[fieldName],
serverURL,
}) })
break break
@@ -146,32 +124,28 @@ export const traverseFields = <T>(args: {
const newRelation = incomingRelation.relationTo const newRelation = incomingRelation.relationTo
if (oldID !== newID || oldRelation !== newRelation) { if (oldID !== newID || oldRelation !== newRelation) {
populationPromises.push( if (!populationsByCollection[newRelation]) {
promise({ populationsByCollection[newRelation] = []
id: incomingRelation.value, }
accessor: 'value',
apiRoute, populationsByCollection[newRelation].push({
collection: newRelation, id: incomingRelation.value,
depth, accessor: 'value',
ref: result[fieldName][i], ref: result[fieldName][i],
serverURL, })
}),
)
} }
} else { } else {
// Handle `hasMany` monomorphic // Handle `hasMany` monomorphic
if (result[fieldName][i]?.id !== incomingRelation) { if (result[fieldName][i]?.id !== incomingRelation) {
populationPromises.push( if (!populationsByCollection[fieldSchema.relationTo]) {
promise({ populationsByCollection[fieldSchema.relationTo] = []
id: incomingRelation, }
accessor: i,
apiRoute, populationsByCollection[fieldSchema.relationTo].push({
collection: String(fieldSchema.relationTo), id: incomingRelation,
depth, accessor: i,
ref: result[fieldName], ref: result[fieldName],
serverURL, })
}),
)
} }
} }
}) })
@@ -217,17 +191,15 @@ export const traverseFields = <T>(args: {
// if the new value is not empty, populate it // if the new value is not empty, populate it
// otherwise set the value to null // otherwise set the value to null
if (newID) { if (newID) {
populationPromises.push( if (!populationsByCollection[newRelation]) {
promise({ populationsByCollection[newRelation] = []
id: newID, }
accessor: 'value',
apiRoute, populationsByCollection[newRelation].push({
collection: newRelation, id: newID,
depth, accessor: 'value',
ref: result[fieldName], ref: result[fieldName],
serverURL, })
}),
)
} else { } else {
result[fieldName] = null result[fieldName] = null
} }
@@ -252,17 +224,15 @@ export const traverseFields = <T>(args: {
// if the new value is not empty, populate it // if the new value is not empty, populate it
// otherwise set the value to null // otherwise set the value to null
if (newID) { if (newID) {
populationPromises.push( if (!populationsByCollection[fieldSchema.relationTo]) {
promise({ populationsByCollection[fieldSchema.relationTo] = []
id: newID, }
accessor: fieldName,
apiRoute, populationsByCollection[fieldSchema.relationTo].push({
collection: String(fieldSchema.relationTo), id: newID,
depth, accessor: fieldName,
ref: result as Record<string, unknown>, ref: result as Record<string, unknown>,
serverURL, })
}),
)
} else { } else {
result[fieldName] = null result[fieldName] = null
} }

View File

@@ -1,19 +1,13 @@
import { promise } from './promise' import type { PopulationsByCollection } from './types'
export const traverseRichText = ({ export const traverseRichText = ({
apiRoute,
depth,
incomingData, incomingData,
populationPromises, populationsByCollection,
result, result,
serverURL,
}: { }: {
apiRoute: string
depth: number
incomingData: any incomingData: any
populationPromises: Promise<any>[] populationsByCollection: PopulationsByCollection
result: any result: any
serverURL: string
}): any => { }): any => {
if (Array.isArray(incomingData)) { if (Array.isArray(incomingData)) {
if (!result) { if (!result) {
@@ -26,12 +20,9 @@ export const traverseRichText = ({
} }
return traverseRichText({ return traverseRichText({
apiRoute,
depth,
incomingData: item, incomingData: item,
populationPromises, populationsByCollection,
result: result[index], result: result[index],
serverURL,
}) })
}) })
} else if (incomingData && typeof incomingData === 'object') { } else if (incomingData && typeof incomingData === 'object') {
@@ -68,26 +59,21 @@ export const traverseRichText = ({
const needsPopulation = !result.value || typeof result.value !== 'object' const needsPopulation = !result.value || typeof result.value !== 'object'
if (needsPopulation) { if (needsPopulation) {
populationPromises.push( if (!populationsByCollection[incomingData.relationTo]) {
promise({ populationsByCollection[incomingData.relationTo] = []
id: incomingData[key], }
accessor: 'value',
apiRoute, populationsByCollection[incomingData.relationTo].push({
collection: incomingData.relationTo, id: incomingData[key],
depth, accessor: 'value',
ref: result, ref: result,
serverURL, })
}),
)
} }
} else { } else {
result[key] = traverseRichText({ result[key] = traverseRichText({
apiRoute,
depth,
incomingData: incomingData[key], incomingData: incomingData[key],
populationPromises, populationsByCollection,
result: result[key], result: result[key],
serverURL,
}) })
} }
}) })

View File

@@ -1,3 +1,11 @@
export type LivePreviewArgs = {} export type LivePreviewArgs = {}
export type LivePreview = void export type LivePreview = void
export type PopulationsByCollection = {
[slug: string]: Array<{
accessor: number | string
id: number | string
ref: Record<string, unknown>
}>
}

View File

@@ -340,7 +340,7 @@ describe('Collections - Live Preview', () => {
returnNumberOfRequests: true, returnNumberOfRequests: true,
}) })
expect(merge1._numberOfRequests).toEqual(4) expect(merge1._numberOfRequests).toEqual(1)
expect(merge1.arrayOfRelationships).toHaveLength(1) expect(merge1.arrayOfRelationships).toHaveLength(1)
expect(merge1.arrayOfRelationships).toMatchObject([ expect(merge1.arrayOfRelationships).toMatchObject([
{ {
@@ -396,7 +396,7 @@ describe('Collections - Live Preview', () => {
returnNumberOfRequests: true, returnNumberOfRequests: true,
}) })
expect(merge2._numberOfRequests).toEqual(4) expect(merge2._numberOfRequests).toEqual(1)
expect(merge2.arrayOfRelationships).toHaveLength(2) expect(merge2.arrayOfRelationships).toHaveLength(2)
expect(merge2.arrayOfRelationships).toMatchObject([ expect(merge2.arrayOfRelationships).toMatchObject([
{ {