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

View File

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

View File

@@ -1,3 +1,11 @@
export type LivePreviewArgs = {}
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,
})
expect(merge1._numberOfRequests).toEqual(4)
expect(merge1._numberOfRequests).toEqual(1)
expect(merge1.arrayOfRelationships).toHaveLength(1)
expect(merge1.arrayOfRelationships).toMatchObject([
{
@@ -396,7 +396,7 @@ describe('Collections - Live Preview', () => {
returnNumberOfRequests: true,
})
expect(merge2._numberOfRequests).toEqual(4)
expect(merge2._numberOfRequests).toEqual(1)
expect(merge2.arrayOfRelationships).toHaveLength(2)
expect(merge2.arrayOfRelationships).toMatchObject([
{