fix: handle caching of card grids on content page blocks

This commit is contained in:
Christel Westerberg
2024-11-01 09:30:04 +01:00
parent a76857a62b
commit 036bb8351a
3 changed files with 79 additions and 97 deletions

View File

@@ -1,11 +1,31 @@
import "server-only" import "server-only"
import deepmerge from "deepmerge"
import { request } from "./request" import { request } from "./request"
import type { BatchRequestDocument } from "graphql-request" import type { BatchRequestDocument } from "graphql-request"
import type { Data } from "@/types/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<T>( export async function batchRequest<T>(
queries: (BatchRequestDocument & { options?: RequestInit })[] queries: (BatchRequestDocument & { options?: RequestInit })[]
): Promise<Data<T>> { ): Promise<Data<T>> {
@@ -17,15 +37,21 @@ export async function batchRequest<T>(
) )
let data = {} as T let data = {} as T
const reasons = [] const reasons: PromiseRejectedResult["reason"][] = []
response.forEach((res) => { response.forEach((res) => {
if (res.status === "fulfilled") { if (res.status === "fulfilled") {
data = Object.assign({}, data, res.value.data) data = deepmerge(data, res.value.data, { arrayMerge })
} else { } else {
reasons.push(res.reason) reasons.push(res.reason)
} }
}) })
if (reasons.length) {
reasons.forEach((reason) => {
console.error(`Batch request failed`, reason)
})
}
return { data } return { data }
} catch (error) { } catch (error) {
console.error("Error in batched graphql request") console.error("Error in batched graphql request")

View File

@@ -1,10 +1,10 @@
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { batchRequest } from "@/lib/graphql/batchRequest"
import { import {
GetContentPage, GetContentPage,
GetContentPageBlocksBatch1, GetContentPageBlocksBatch1,
GetContentPageBlocksBatch2, GetContentPageBlocksBatch2,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { request } from "@/lib/graphql/request"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { contentPageSchema } from "./output" import { contentPageSchema } from "./output"
@@ -12,30 +12,20 @@ import {
fetchContentPageRefs, fetchContentPageRefs,
generatePageTags, generatePageTags,
getContentPageCounter, getContentPageCounter,
validateContentPageRefs,
} from "./utils" } from "./utils"
import { import {
TrackingChannelEnum, TrackingChannelEnum,
type TrackingSDKPageData, type TrackingSDKPageData,
} from "@/types/components/tracking" } from "@/types/components/tracking"
import { ContentPageEnum } from "@/types/enums/contentPage" import type { GetContentPageSchema } from "@/types/trpc/routers/contentstack/contentPage"
import type {
GetBlock,
GetContentPageSchema,
} from "@/types/trpc/routers/contentstack/contentPage"
export const contentPageQueryRouter = router({ export const contentPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = 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) { if (!contentPageRefs) {
return null return null
} }
@@ -50,69 +40,42 @@ export const contentPageQueryRouter = router({
}) })
) )
const [mainResponse, blocksResponse1, blocksResponse2] = await Promise.all([ const contentPageRequest = await batchRequest<GetContentPageSchema>([
request<GetContentPageSchema>( {
GetContentPage, document: GetContentPage,
{ locale: lang, uid }, variables: { locale: lang, uid },
{ options: {
cache: "force-cache", cache: "force-cache",
next: { next: {
tags, tags,
}, },
} },
), },
request<GetContentPageSchema>(
GetContentPageBlocksBatch1, {
{ locale: lang, uid }, document: GetContentPageBlocksBatch1,
{ variables: { locale: lang, uid },
options: {
cache: "force-cache", cache: "force-cache",
next: { next: {
tags, tags,
}, },
} },
), },
request<GetContentPageSchema>(
GetContentPageBlocksBatch2, {
{ locale: lang, uid }, document: GetContentPageBlocksBatch2,
{ variables: { locale: lang, uid },
options: {
cache: "force-cache", cache: "force-cache",
next: { next: {
tags, tags,
}, },
} },
), },
]) ])
const blocksOrder = mainResponse.data.content_page.blocks?.map( const contentPage = contentPageSchema.safeParse(contentPageRequest.data)
(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)
if (!contentPage.success) { if (!contentPage.success) {
console.error( console.error(
`Failed to validate Contentpage Data - (lang: ${lang}, uid: ${uid})` `Failed to validate Contentpage Data - (lang: ${lang}, uid: ${uid})`

View File

@@ -1,11 +1,11 @@
import { metrics } from "@opentelemetry/api" import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { batchRequest } from "@/lib/graphql/batchRequest"
import { import {
GetContentPageBlocksRefs, GetContentPageBlocksRefs,
GetContentPageRefs, GetContentPageRefs,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc" import { notFound } from "@/server/errors/trpc"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag" import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
@@ -44,30 +44,30 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
query: { lang, uid }, query: { lang, uid },
}) })
) )
const [mainRefsResponse, blockRefsResponse] = await Promise.all([ const res = await batchRequest<GetContentPageRefsSchema>([
request<GetContentPageRefsSchema>( {
GetContentPageRefs, document: GetContentPageRefs,
{ locale: lang, uid }, variables: { locale: lang, uid },
{ options: {
cache: "force-cache", cache: "force-cache",
next: { next: {
tags: [generateTag(lang, uid)], tags: [generateTag(lang, uid)],
}, },
} },
), },
request<GetContentPageRefsSchema>( {
GetContentPageBlocksRefs, document: GetContentPageBlocksRefs,
{ locale: lang, uid }, variables: { locale: lang, uid },
{ options: {
cache: "force-cache", cache: "force-cache",
next: { next: {
tags: [generateTag(lang, uid)], tags: [generateTag(lang, uid + 1)],
}, },
} },
), },
]) ])
if (!mainRefsResponse.data) { if (!res.data) {
const notFoundError = notFound(mainRefsResponse) const notFoundError = notFound(res)
getContentPageRefsFailCounter.add(1, { getContentPageRefsFailCounter.add(1, {
lang, lang,
uid, uid,
@@ -88,22 +88,7 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
) )
throw notFoundError throw notFoundError
} }
const responseData = { const validatedData = contentPageRefsSchema.safeParse(res.data)
...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)
if (!validatedData.success) { if (!validatedData.success) {
getContentPageRefsFailCounter.add(1, { getContentPageRefsFailCounter.add(1, {
lang, lang,
@@ -192,6 +177,14 @@ export function getConnections({ content_page }: ContentPageRefs) {
} }
break break
} }
case ContentPageEnum.ContentStack.blocks.CardsGrid: {
if (block.cards_grid.length) {
block.cards_grid.forEach((card) => {
connections.push(card)
})
}
break
}
} }
}) })
} }