feat(SW-187): refactor footer query

This commit is contained in:
Pontus Dreij
2024-09-09 10:22:17 +02:00
parent 21d33a0079
commit f2068ac325
5 changed files with 244 additions and 133 deletions

View File

@@ -1,15 +1,11 @@
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import { getLang } from "@/i18n/serverContext"
import FooterDetails from "./Details" import FooterDetails from "./Details"
import FooterNavigation from "./Navigation" import FooterNavigation from "./Navigation"
export default async function Footer() { export default async function Footer() {
const footerData = await serverClient().contentstack.base.footer({
lang: getLang(),
})
const languages = await serverClient().contentstack.languageSwitcher.get() const languages = await serverClient().contentstack.languageSwitcher.get()
const footerData = await serverClient().contentstack.base.footer()
if (!footerData || !languages) { if (!footerData || !languages) {
return <FooterDetails /> return <FooterDetails />

View File

@@ -291,14 +291,12 @@ const validateInternalLink = z
}) })
.optional() .optional()
const validateLinkItem = z.object({ export const validateLinkItem = z.object({
open_in_new_tab: z.boolean(), open_in_new_tab: z.boolean(),
link: validateExternalLink, link: validateExternalLink,
pageConnection: validateInternalLink, pageConnection: validateInternalLink,
}) })
export type FooterLinkItem = z.infer<typeof validateLinkItem>
export const validateFooterConfigSchema = z.object({ export const validateFooterConfigSchema = z.object({
all_footer: z.object({ all_footer: z.object({
items: z.array( items: z.array(
@@ -333,9 +331,6 @@ export const validateFooterConfigSchema = z.object({
}), }),
}) })
export type FooterDataRaw = z.infer<typeof validateFooterConfigSchema>
export type FooterData = FooterDataRaw["all_footer"]["items"][0]
const pageConnectionRefs = z.object({ const pageConnectionRefs = z.object({
edges: z.array( edges: z.array(
z.object({ z.object({
@@ -350,7 +345,7 @@ const pageConnectionRefs = z.object({
), ),
}) })
const validateFooterRefConfigSchema = z.object({ export const validateFooterRefConfigSchema = z.object({
all_footer: z.object({ all_footer: z.object({
items: z.array( items: z.array(
z.object({ z.object({
@@ -382,8 +377,6 @@ const validateFooterRefConfigSchema = z.object({
}), }),
}) })
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
const linkConnectionNodeSchema = z const linkConnectionNodeSchema = z
.object({ .object({
edges: z edges: z

View File

@@ -29,17 +29,24 @@ import {
CurrentHeaderData, CurrentHeaderData,
CurrentHeaderDataRaw, CurrentHeaderDataRaw,
CurrentHeaderRefDataRaw, CurrentHeaderRefDataRaw,
FooterDataRaw,
FooterRefDataRaw,
getHeaderRefSchema, getHeaderRefSchema,
getHeaderSchema, getHeaderSchema,
validateContactConfigSchema, validateContactConfigSchema,
validateCurrentFooterConfigSchema, validateCurrentFooterConfigSchema,
validateCurrentHeaderConfigSchema, validateCurrentHeaderConfigSchema,
validateFooterConfigSchema, validateFooterConfigSchema,
validateFooterRefConfigSchema,
} from "./output" } from "./output"
import { getConnections, transformPageConnectionLinks } from "./utils" import {
getConnections,
getFooterConnections,
transformPageConnectionLinks,
} from "./utils"
import type {
FooterDataRaw,
FooterRefDataRaw,
} from "@/types/components/footer/footer"
import type { HeaderRefResponse, HeaderResponse } from "@/types/header" import type { HeaderRefResponse, HeaderResponse } from "@/types/header"
const meter = metrics.getMeter("trpc.contentstack.base") const meter = metrics.getMeter("trpc.contentstack.base")
@@ -90,6 +97,27 @@ const getHeaderSuccessCounter = meter.createCounter(
const getHeaderFailCounter = meter.createCounter( const getHeaderFailCounter = meter.createCounter(
"trpc.contentstack.header.get-fail" "trpc.contentstack.header.get-fail"
) )
// OpenTelemetry metrics: CurrentHeader
const getCurrentFooterRefCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get"
)
const getCurrentFooterRefSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-success"
)
const getCurrentFooterRefFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-fail"
)
const getCurrentFooterCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get"
)
const getCurrentFooterSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-success"
)
const getCurrentFooterFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-fail"
)
// OpenTelemetry metrics: Footer // OpenTelemetry metrics: Footer
const getFooterRefCounter = meter.createCounter( const getFooterRefCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get" "trpc.contentstack.footer.ref.get"
@@ -403,9 +431,9 @@ export const baseQueryRouter = router({
currentFooter: contentstackBaseProcedure currentFooter: contentstackBaseProcedure
.input(langInput) .input(langInput)
.query(async ({ input }) => { .query(async ({ input }) => {
getFooterRefCounter.add(1, { lang: input.lang }) getCurrentFooterRefCounter.add(1, { lang: input.lang })
console.info( console.info(
"contentstack.footer.ref start", "contentstack.currentFooter.ref start",
JSON.stringify({ query: { lang: input.lang } }) JSON.stringify({ query: { lang: input.lang } })
) )
const responseRef = await request<CurrentFooterRefDataRaw>( const responseRef = await request<CurrentFooterRefDataRaw>(
@@ -421,9 +449,9 @@ export const baseQueryRouter = router({
} }
) )
// There's currently no error handling/validation for the responseRef, should it be added? // There's currently no error handling/validation for the responseRef, should it be added?
getFooterCounter.add(1, { lang: input.lang }) getCurrentFooterCounter.add(1, { lang: input.lang })
console.info( console.info(
"contentstack.footer start", "contentstack.currentFooter start",
JSON.stringify({ JSON.stringify({
query: { query: {
lang: input.lang, lang: input.lang,
@@ -447,13 +475,13 @@ export const baseQueryRouter = router({
if (!response.data) { if (!response.data) {
const notFoundError = notFound(response) const notFoundError = notFound(response)
getFooterFailCounter.add(1, { getCurrentFooterFailCounter.add(1, {
lang: input.lang, lang: input.lang,
error_type: "not_found", error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }), error: JSON.stringify({ code: notFoundError.code }),
}) })
console.error( console.error(
"contentstack.footer not found error", "contentstack.currentFooter not found error",
JSON.stringify({ JSON.stringify({
query: { query: {
lang: input.lang, lang: input.lang,
@@ -464,142 +492,194 @@ export const baseQueryRouter = router({
throw notFoundError throw notFoundError
} }
const validatedFooterConfig = validateCurrentFooterConfigSchema.safeParse( const validatedCurrentFooterConfig =
response.data validateCurrentFooterConfigSchema.safeParse(response.data)
)
if (!validatedFooterConfig.success) { if (!validatedCurrentFooterConfig.success) {
getFooterFailCounter.add(1, { getFooterFailCounter.add(1, {
lang: input.lang, lang: input.lang,
error_type: "validation_error", error_type: "validation_error",
error: JSON.stringify(validatedFooterConfig.error), error: JSON.stringify(validatedCurrentFooterConfig.error),
}) })
console.error( console.error(
"contentstack.footer validation error", "contentstack.currentFooter validation error",
JSON.stringify({ JSON.stringify({
query: { lang: input.lang }, query: { lang: input.lang },
error: validatedFooterConfig.error, error: validatedCurrentFooterConfig.error,
}) })
) )
return null return null
} }
getFooterSuccessCounter.add(1, { lang: input.lang }) getCurrentFooterSuccessCounter.add(1, { lang: input.lang })
console.info( console.info(
"contentstack.footer success", "contentstack.currentFooter success",
JSON.stringify({ query: { lang: input.lang } }) JSON.stringify({ query: { lang: input.lang } })
) )
return validatedFooterConfig.data.all_current_footer.items[0] return validatedCurrentFooterConfig.data.all_current_footer.items[0]
}), }),
footer: contentstackBaseProcedure footer: contentstackBaseProcedure.query(async ({ ctx }) => {
.input(langInput) const { lang } = ctx
.query(async ({ input }) => { getFooterRefCounter.add(1, { lang })
getFooterRefCounter.add(1, { lang: input.lang }) console.info(
console.info( "contentstack.footer.ref start",
"contentstack.footer.ref start", JSON.stringify({ query: { lang } })
JSON.stringify({ query: { lang: input.lang } }) )
) const responseRef = await request<FooterRefDataRaw>(
const responseRef = await request<FooterRefDataRaw>( GetFooterRef,
GetFooterRef, {
{ locale: lang,
locale: input.lang, },
{
cache: "force-cache",
next: {
tags: [generateRefsResponseTag(lang, "footer")],
}, },
{ }
cache: "force-cache", )
next: {
tags: [generateRefsResponseTag(input.lang, "footer")],
},
}
)
// There's currently no error handling/validation for the responseRef, should it be added? if (!responseRef.data) {
getFooterCounter.add(1, { lang: input.lang }) const notFoundError = notFound(responseRef)
console.info( getFooterRefFailCounter.add(1, {
"contentstack.footer start", lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer.refs not found error",
JSON.stringify({ JSON.stringify({
query: { query: {
lang: input.lang, lang,
}, },
error: { code: notFoundError.code },
}) })
) )
const footerUID = responseRef.data.all_footer.items[0].system.uid throw notFoundError
const response = await request<FooterDataRaw>( }
GetFooter,
{ const validatedFooterRefs = validateFooterRefConfigSchema.safeParse(
locale: input.lang, responseRef.data
)
if (!validatedFooterRefs.success) {
getFooterRefFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedFooterRefs.error),
})
console.error(
"contentstack.footer.refs validation error",
JSON.stringify({
query: {
lang,
},
error: validatedFooterRefs.error,
})
)
return null
}
getFooterRefSuccessCounter.add(1, { lang })
console.info(
"contentstack.footer.refs success",
JSON.stringify({ query: { lang } })
)
const connections = getFooterConnections(validatedFooterRefs.data)
const footerUID = responseRef.data.all_footer.items[0].system.uid
// There's currently no error handling/validation for the responseRef, should it be added?
getFooterCounter.add(1, { lang: lang })
console.info(
"contentstack.footer start",
JSON.stringify({
query: {
lang,
}, },
{ })
cache: "force-cache", )
next: { const tags = [
tags: [generateTag(input.lang, footerUID)], generateTags(lang, connections),
generateTag(lang, footerUID),
].flat()
const response = await request<FooterDataRaw>(
GetFooter,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags,
},
}
)
if (!response.data) {
const notFoundError = notFound(response)
getFooterFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer not found error",
JSON.stringify({
query: {
lang,
}, },
} error: { code: notFoundError.code },
)
if (!response.data) {
const notFoundError = notFound(response)
getFooterFailCounter.add(1, {
lang: input.lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer not found error",
JSON.stringify({
query: {
lang: input.lang,
},
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const validatedFooterConfig = validateFooterConfigSchema.safeParse(
response.data
)
if (!validatedFooterConfig.success) {
getFooterFailCounter.add(1, {
lang: input.lang,
error_type: "validation_error",
error: JSON.stringify(validatedFooterConfig.error),
})
console.error(
"contentstack.footer validation error",
JSON.stringify({
query: { lang: input.lang },
error: validatedFooterConfig.error,
})
)
return null
}
getFooterSuccessCounter.add(1, { lang: input.lang })
console.info(
"contentstack.footer success",
JSON.stringify({ query: { lang: input.lang } })
)
const validatedFooterData = validatedFooterConfig.data.all_footer.items[0]
const mainLinks = transformPageConnectionLinks(
validatedFooterData.main_links
)
const secondaryLinks = validatedFooterData.secondary_links.map(
(section) => ({
title: section.title,
links: transformPageConnectionLinks(section.links),
}) })
) )
throw notFoundError
}
const tertiaryLinks = transformPageConnectionLinks( const validatedFooterConfig = validateFooterConfigSchema.safeParse(
validatedFooterData.tertiary_links response.data
)
if (!validatedFooterConfig.success) {
getFooterFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedFooterConfig.error),
})
console.error(
"contentstack.footer validation error",
JSON.stringify({
query: { lang: lang },
error: validatedFooterConfig.error,
})
) )
return null
}
getFooterSuccessCounter.add(1, { lang })
console.info(
"contentstack.footer success",
JSON.stringify({ query: { lang } })
)
const validatedFooterData = validatedFooterConfig.data.all_footer.items[0]
const mainLinks = transformPageConnectionLinks(
validatedFooterData.main_links
)
return { const secondaryLinks = validatedFooterData.secondary_links.map(
mainLinks: mainLinks, (section) => ({
appDownloads: validatedFooterData.app_downloads, title: section.title,
secondaryLinks: secondaryLinks, links: transformPageConnectionLinks(section.links),
socialMedia: validatedFooterData.social_media, })
tertiaryLinks: tertiaryLinks, )
}
}), const tertiaryLinks = transformPageConnectionLinks(
validatedFooterData.tertiary_links
)
return {
mainLinks: mainLinks,
appDownloads: validatedFooterData.app_downloads,
secondaryLinks: secondaryLinks,
socialMedia: validatedFooterData.social_media,
tertiaryLinks: tertiaryLinks,
}
}),
}) })

View File

@@ -1,7 +1,10 @@
import { HeaderRefResponse } from "@/types/header" import type {
FooterLinkItem,
FooterRefDataRaw,
} from "@/types/components/footer/footer"
import type { HeaderRefResponse } from "@/types/header"
import { Edges } from "@/types/requests/utils/edges" import { Edges } from "@/types/requests/utils/edges"
import { NodeRefs } from "@/types/requests/utils/refs" import { NodeRefs } from "@/types/requests/utils/refs"
import type { FooterLinkItem } from "./output"
export function getConnections(refs: HeaderRefResponse) { export function getConnections(refs: HeaderRefResponse) {
const connections: Edges<NodeRefs>[] = [] const connections: Edges<NodeRefs>[] = []
@@ -40,6 +43,33 @@ export function getConnections(refs: HeaderRefResponse) {
return connections return connections
} }
export function getFooterConnections(refs: FooterRefDataRaw) {
const connections: Edges<NodeRefs>[] = []
const footerData = refs.all_footer.items[0]
const mainLinks = footerData.main_links
const secondaryLinks = footerData.secondary_links
const tertiaryLinks = footerData.tertiary_links
if (mainLinks) {
mainLinks.forEach(({ pageConnection }) => {
connections.push(pageConnection)
})
}
secondaryLinks?.forEach(({ links }) => {
if (links) {
links.forEach(({ pageConnection }) => {
connections.push(pageConnection)
})
}
})
if (tertiaryLinks) {
tertiaryLinks.forEach(({ pageConnection }) => {
connections.push(pageConnection)
})
}
return connections
}
export function transformPageConnectionLinks(links: FooterLinkItem[]) { export function transformPageConnectionLinks(links: FooterLinkItem[]) {
if (!links) return [] if (!links) return []
return links.flatMap((link) => { return links.flatMap((link) => {

View File

@@ -0,0 +1,12 @@
import { z } from "zod"
import {
validateFooterConfigSchema,
validateFooterRefConfigSchema,
validateLinkItem,
} from "@/server/routers/contentstack/base/output"
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
export type FooterDataRaw = z.infer<typeof validateFooterConfigSchema>
export type FooterData = FooterDataRaw["all_footer"]["items"][0]
export type FooterLinkItem = z.infer<typeof validateLinkItem>