diff --git a/lib/graphql/batchRequest.ts b/lib/graphql/batchRequest.ts index 4cbd0ee65..b6b5dbe3f 100644 --- a/lib/graphql/batchRequest.ts +++ b/lib/graphql/batchRequest.ts @@ -1,11 +1,31 @@ import "server-only" +import deepmerge from "deepmerge" + import { request } from "./request" import type { BatchRequestDocument } from "graphql-request" import type { Data } from "@/types/request" +function arrayMerge( + target: any[], + source: any[], + options: deepmerge.ArrayMergeOptions | undefined +) { + const destination = target.slice() + source.forEach((item, index) => { + if (typeof destination[index] === "undefined") { + destination[index] = options?.cloneUnlessOtherwiseSpecified(item, options) + } else if (options?.isMergeableObject(item)) { + destination[index] = deepmerge(target[index], item, options) + } else if (target.indexOf(item) === -1) { + destination.push(item) + } + }) + return destination +} + export async function batchRequest( queries: (BatchRequestDocument & { options?: RequestInit })[] ): Promise> { @@ -17,15 +37,21 @@ export async function batchRequest( ) let data = {} as T - const reasons = [] + const reasons: PromiseRejectedResult["reason"][] = [] response.forEach((res) => { if (res.status === "fulfilled") { - data = Object.assign({}, data, res.value.data) + data = deepmerge(data, res.value.data, { arrayMerge }) } else { reasons.push(res.reason) } }) + if (reasons.length) { + reasons.forEach((reason) => { + console.error(`Batch request failed`, reason) + }) + } + return { data } } catch (error) { console.error("Error in batched graphql request") diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts index e7ec7e24c..4655f4b0e 100644 --- a/server/routers/contentstack/contentPage/query.ts +++ b/server/routers/contentstack/contentPage/query.ts @@ -1,10 +1,10 @@ import { Lang } from "@/constants/languages" +import { batchRequest } from "@/lib/graphql/batchRequest" import { GetContentPage, GetContentPageBlocksBatch1, GetContentPageBlocksBatch2, } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" -import { request } from "@/lib/graphql/request" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { contentPageSchema } from "./output" @@ -12,30 +12,20 @@ import { fetchContentPageRefs, generatePageTags, getContentPageCounter, - validateContentPageRefs, } from "./utils" import { TrackingChannelEnum, type TrackingSDKPageData, } from "@/types/components/tracking" -import { ContentPageEnum } from "@/types/enums/contentPage" -import type { - GetBlock, - GetContentPageSchema, -} from "@/types/trpc/routers/contentstack/contentPage" +import type { GetContentPageSchema } from "@/types/trpc/routers/contentstack/contentPage" export const contentPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx - const contentPageRefsData = await fetchContentPageRefs(lang, uid) + const contentPageRefs = await fetchContentPageRefs(lang, uid) - const contentPageRefs = validateContentPageRefs( - contentPageRefsData, - lang, - uid - ) if (!contentPageRefs) { return null } @@ -50,69 +40,42 @@ export const contentPageQueryRouter = router({ }) ) - const [mainResponse, blocksResponse1, blocksResponse2] = await Promise.all([ - request( - GetContentPage, - { locale: lang, uid }, - { + const contentPageRequest = await batchRequest([ + { + document: GetContentPage, + variables: { locale: lang, uid }, + options: { cache: "force-cache", next: { tags, }, - } - ), - request( - GetContentPageBlocksBatch1, - { locale: lang, uid }, - { + }, + }, + + { + document: GetContentPageBlocksBatch1, + variables: { locale: lang, uid }, + options: { cache: "force-cache", next: { tags, }, - } - ), - request( - GetContentPageBlocksBatch2, - { locale: lang, uid }, - { + }, + }, + + { + document: GetContentPageBlocksBatch2, + variables: { locale: lang, uid }, + options: { cache: "force-cache", next: { tags, }, - } - ), + }, + }, ]) - const blocksOrder = mainResponse.data.content_page.blocks?.map( - (block) => block.__typename - ) - - let sortedBlocks - if (blocksOrder) { - const blocks = [ - blocksResponse1.data.content_page.blocks, - blocksResponse2.data.content_page.blocks, - ] - .flat(2) - .filter((obj) => !(obj && Object.keys(obj).length < 2)) - // Remove empty objects and objects with only typename - - sortedBlocks = blocksOrder - .map((typename: ContentPageEnum.ContentStack.blocks) => - blocks.find((block) => block?.__typename === typename) - ) - .filter((block): block is GetBlock => !!block) - } - - const responseData = { - ...mainResponse.data, - content_page: { - ...mainResponse.data.content_page, - blocks: sortedBlocks, - }, - } - - const contentPage = contentPageSchema.safeParse(responseData) + const contentPage = contentPageSchema.safeParse(contentPageRequest.data) if (!contentPage.success) { console.error( `Failed to validate Contentpage Data - (lang: ${lang}, uid: ${uid})` diff --git a/server/routers/contentstack/contentPage/utils.ts b/server/routers/contentstack/contentPage/utils.ts index 092595813..4364d5d4b 100644 --- a/server/routers/contentstack/contentPage/utils.ts +++ b/server/routers/contentstack/contentPage/utils.ts @@ -1,11 +1,11 @@ import { metrics } from "@opentelemetry/api" import { Lang } from "@/constants/languages" +import { batchRequest } from "@/lib/graphql/batchRequest" import { GetContentPageBlocksRefs, GetContentPageRefs, } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" -import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" import { generateTag, generateTagsFromSystem } from "@/utils/generateTag" @@ -44,30 +44,30 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) { query: { lang, uid }, }) ) - const [mainRefsResponse, blockRefsResponse] = await Promise.all([ - request( - GetContentPageRefs, - { locale: lang, uid }, - { + const res = await batchRequest([ + { + document: GetContentPageRefs, + variables: { locale: lang, uid }, + options: { cache: "force-cache", next: { tags: [generateTag(lang, uid)], }, - } - ), - request( - GetContentPageBlocksRefs, - { locale: lang, uid }, - { + }, + }, + { + document: GetContentPageBlocksRefs, + variables: { locale: lang, uid }, + options: { cache: "force-cache", next: { - tags: [generateTag(lang, uid)], + tags: [generateTag(lang, uid + 1)], }, - } - ), + }, + }, ]) - if (!mainRefsResponse.data) { - const notFoundError = notFound(mainRefsResponse) + if (!res.data) { + const notFoundError = notFound(res) getContentPageRefsFailCounter.add(1, { lang, uid, @@ -88,22 +88,7 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) { ) throw notFoundError } - const responseData = { - ...mainRefsResponse.data, - content_page: { - ...mainRefsResponse.data.content_page, - blocks: blockRefsResponse.data.content_page.blocks, - }, - } - return responseData -} - -export function validateContentPageRefs( - data: GetContentPageRefsSchema, - lang: Lang, - uid: string -) { - const validatedData = contentPageRefsSchema.safeParse(data) + const validatedData = contentPageRefsSchema.safeParse(res.data) if (!validatedData.success) { getContentPageRefsFailCounter.add(1, { lang, @@ -192,6 +177,14 @@ export function getConnections({ content_page }: ContentPageRefs) { } break } + case ContentPageEnum.ContentStack.blocks.CardsGrid: { + if (block.cards_grid.length) { + block.cards_grid.forEach((card) => { + connections.push(card) + }) + } + break + } } }) }