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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user