Merged in feat/sw-187-footer-content-stack (pull request #565)

Feat/sw 187 footer content stack

Approved-by: Erik Tiekstra
Approved-by: Matilda Landström
This commit is contained in:
Pontus Dreij
2024-09-12 07:11:25 +00:00
63 changed files with 1350 additions and 521 deletions

View File

@@ -175,7 +175,7 @@ const validateNavigationItem = z.object({
export type NavigationItem = z.infer<typeof validateNavigationItem>
export const validateFooterConfigSchema = z.object({
export const validateCurrentFooterConfigSchema = z.object({
all_current_footer: z.object({
items: z.array(
z.object({
@@ -242,16 +242,18 @@ export const validateFooterConfigSchema = z.object({
}),
})
export type FooterDataRaw = z.infer<typeof validateFooterConfigSchema>
export type CurrentFooterDataRaw = z.infer<
typeof validateCurrentFooterConfigSchema
>
export type FooterData = Omit<
FooterDataRaw["all_current_footer"]["items"][0],
export type CurrentFooterData = Omit<
CurrentFooterDataRaw["all_current_footer"]["items"][0],
"logoConnection"
> & {
logo: Image
}
const validateFooterRefConfigSchema = z.object({
const validateCurrentFooterRefConfigSchema = z.object({
all_current_footer: z.object({
items: z.array(
z.object({
@@ -264,7 +266,170 @@ const validateFooterRefConfigSchema = z.object({
}),
})
export type FooterRefDataRaw = z.infer<typeof validateFooterRefConfigSchema>
export type CurrentFooterRefDataRaw = z.infer<
typeof validateCurrentFooterRefConfigSchema
>
const validateExternalLink = z
.object({
href: z.string(),
title: z.string(),
})
.optional()
const validateInternalLink = z
.object({
edges: z
.array(
z.object({
node: z.object({
system: z.object({
uid: z.string(),
locale: z.nativeEnum(Lang),
}),
url: z.string(),
title: z.string(),
web: z
.object({
original_url: z.string(),
})
.optional(),
}),
})
)
.max(1),
})
.transform((data) => {
const node = data.edges[0]?.node
if (!node) {
return null
}
const url = node.url
const originalUrl = node.web?.original_url
const lang = node.system.locale
return {
url: originalUrl ?? removeMultipleSlashes(`/${lang}/${url}`),
title: node.title,
}
})
.optional()
export const validateLinkItem = z
.object({
title: z.string(),
open_in_new_tab: z.boolean(),
link: validateExternalLink,
pageConnection: validateInternalLink,
})
.transform((data) => {
return {
url: data.pageConnection?.url ?? data.link?.href ?? "",
title: data?.title ?? data.link?.title,
openInNewTab: data.open_in_new_tab,
isExternal: !!data.link?.href,
}
})
export const validateSecondaryLinks = z.array(
z.object({
title: z.string(),
links: z.array(validateLinkItem),
})
)
export const validateLinksWithType = z.array(
z.object({
type: z.string(),
href: validateExternalLink,
})
)
export const validateFooterConfigSchema = z
.object({
all_footer: z.object({
items: z.array(
z.object({
main_links: z.array(validateLinkItem),
app_downloads: z.object({
title: z.string(),
links: validateLinksWithType,
}),
secondary_links: validateSecondaryLinks,
social_media: z.object({
links: validateLinksWithType,
}),
tertiary_links: z.array(validateLinkItem),
})
),
}),
})
.transform((data) => {
const {
main_links,
app_downloads,
secondary_links,
social_media,
tertiary_links,
} = data.all_footer.items[0]
return {
mainLinks: main_links,
appDownloads: app_downloads,
secondaryLinks: secondary_links,
socialMedia: social_media,
tertiaryLinks: tertiary_links,
}
})
const pageConnectionRefs = z.object({
edges: z
.array(
z.object({
node: z.object({
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
}),
})
)
.max(1),
})
export const validateFooterRefConfigSchema = z.object({
all_footer: z.object({
items: z
.array(
z.object({
main_links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
secondary_links: z.array(
z.object({
links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
})
),
tertiary_links: z.array(
z.object({
pageConnection: pageConnectionRefs,
})
),
system: z.object({
content_type_uid: z.string(),
uid: z.string(),
}),
})
)
.length(1),
}),
})
const linkConnectionNodeSchema = z
.object({
@@ -333,7 +498,7 @@ const cardButtonSchema = z
const href =
isContentstackLink && externalLink.href
? externalLink.href
: linkConnectionData?.href || ""
: linkConnectionData?.href ?? ""
return {
openInNewTab: data.open_in_new_tab,

View File

@@ -9,6 +9,7 @@ import {
GetCurrentHeader,
GetCurrentHeaderRef,
} from "@/lib/graphql/Query/CurrentHeader.graphql"
import { GetFooter, GetFooterRef } from "@/lib/graphql/Query/Footer.graphql"
import { GetHeader, GetHeaderRef } from "@/lib/graphql/Query/Header.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
@@ -23,19 +24,25 @@ import {
import { langInput } from "./input"
import {
type ContactConfigData,
CurrentFooterDataRaw,
CurrentFooterRefDataRaw,
CurrentHeaderData,
CurrentHeaderDataRaw,
CurrentHeaderRefDataRaw,
FooterDataRaw,
FooterRefDataRaw,
getHeaderRefSchema,
getHeaderSchema,
validateContactConfigSchema,
validateCurrentFooterConfigSchema,
validateCurrentHeaderConfigSchema,
validateFooterConfigSchema,
validateFooterRefConfigSchema,
} from "./output"
import { getConnections } from "./utils"
import { getConnections, getFooterConnections } from "./utils"
import type {
FooterDataRaw,
FooterRefDataRaw,
} from "@/types/components/footer/footer"
import type { HeaderRefResponse, HeaderResponse } from "@/types/header"
const meter = metrics.getMeter("trpc.contentstack.base")
@@ -86,6 +93,27 @@ const getHeaderSuccessCounter = meter.createCounter(
const getHeaderFailCounter = meter.createCounter(
"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
const getFooterRefCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get"
@@ -396,15 +424,15 @@ export const baseQueryRouter = router({
logo,
} as CurrentHeaderData
}),
footer: contentstackBaseProcedure
currentFooter: contentstackBaseProcedure
.input(langInput)
.query(async ({ input }) => {
getFooterRefCounter.add(1, { lang: input.lang })
getCurrentFooterRefCounter.add(1, { lang: input.lang })
console.info(
"contentstack.footer.ref start",
"contentstack.currentFooter.ref start",
JSON.stringify({ query: { lang: input.lang } })
)
const responseRef = await request<FooterRefDataRaw>(
const responseRef = await request<CurrentFooterRefDataRaw>(
GetCurrentFooterRef,
{
locale: input.lang,
@@ -417,9 +445,9 @@ export const baseQueryRouter = router({
}
)
// 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(
"contentstack.footer start",
"contentstack.currentFooter start",
JSON.stringify({
query: {
lang: input.lang,
@@ -428,13 +456,13 @@ export const baseQueryRouter = router({
)
const currentFooterUID =
responseRef.data.all_current_footer.items[0].system.uid
const response = await request<FooterDataRaw>(
const response = await request<CurrentFooterDataRaw>(
GetCurrentFooter,
{
locale: input.lang,
},
{
cache: "force-cache",
next: {
tags: [generateTag(input.lang, currentFooterUID)],
},
@@ -443,13 +471,13 @@ export const baseQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getFooterFailCounter.add(1, {
getCurrentFooterFailCounter.add(1, {
lang: input.lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer not found error",
"contentstack.currentFooter not found error",
JSON.stringify({
query: {
lang: input.lang,
@@ -460,30 +488,172 @@ export const baseQueryRouter = router({
throw notFoundError
}
const validatedFooterConfig = validateFooterConfigSchema.safeParse(
response.data
)
const validatedCurrentFooterConfig =
validateCurrentFooterConfigSchema.safeParse(response.data)
if (!validatedFooterConfig.success) {
if (!validatedCurrentFooterConfig.success) {
getFooterFailCounter.add(1, {
lang: input.lang,
error_type: "validation_error",
error: JSON.stringify(validatedFooterConfig.error),
error: JSON.stringify(validatedCurrentFooterConfig.error),
})
console.error(
"contentstack.footer validation error",
"contentstack.currentFooter validation error",
JSON.stringify({
query: { lang: input.lang },
error: validatedFooterConfig.error,
error: validatedCurrentFooterConfig.error,
})
)
return null
}
getFooterSuccessCounter.add(1, { lang: input.lang })
getCurrentFooterSuccessCounter.add(1, { lang: input.lang })
console.info(
"contentstack.footer success",
"contentstack.currentFooter success",
JSON.stringify({ query: { lang: input.lang } })
)
return validatedFooterConfig.data.all_current_footer.items[0]
return validatedCurrentFooterConfig.data.all_current_footer.items[0]
}),
footer: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
getFooterRefCounter.add(1, { lang })
console.info(
"contentstack.footer.ref start",
JSON.stringify({ query: { lang } })
)
const responseRef = await request<FooterRefDataRaw>(
GetFooterRef,
{
locale: lang,
},
{
cache: "force-cache",
next: {
tags: [generateRefsResponseTag(lang, "footer")],
},
}
)
if (!responseRef.data) {
const notFoundError = notFound(responseRef)
getFooterRefFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer.refs not found error",
JSON.stringify({
query: {
lang,
},
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const validatedFooterRefs = validateFooterRefConfigSchema.safeParse(
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
getFooterCounter.add(1, { lang: lang })
console.info(
"contentstack.footer start",
JSON.stringify({
query: {
lang,
},
})
)
const tags = [
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 },
})
)
throw notFoundError
}
const validatedFooterConfig = validateFooterConfigSchema.safeParse(
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 } })
)
return validatedFooterConfig.data
}),
})

View File

@@ -1,4 +1,8 @@
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 { NodeRefs } from "@/types/requests/utils/refs"
@@ -38,3 +42,30 @@ export function getConnections(refs: HeaderRefResponse) {
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
}